diff --git a/assets/do_di/AAGO.md b/assets/do_di/AAGO.md deleted file mode 100644 index 797cbfa..0000000 --- a/assets/do_di/AAGO.md +++ /dev/null @@ -1,87 +0,0 @@ -cta de Asamblea General Ordinaria N°1 -## Decentralized Climate Foundation A.C. - - -En la Ciudad de México, Delegación Benito Juárez, a los 31 días del mes de Diciembre del año 2022, en el domicilio de la calle San Antonio Nº 123, Departamento 03, en las oficinas de la Decentralized Climate Foundation A.C., siendo las 17:00 horas, y habiéndose reunido el Quórum necesario conforme las directrices del Acta Constitutiva, se da comienzo a la Asamblea General Ordinaria, convocada por la Comisión Directiva, a fin de tratar el siguiente Orden del Día: - -1) Lectura del Acta Constitutiva e Informe de Actividades Realizadas -2) Tratamiento y aprobación del Balance Anual correspondiente al primer año de actividades -3) Elección de los cargos Presidente y Secretario -4) Elección de los cargos de Tesorero -___ - -Esta Asamblea es presidida por el C. Luis Alberto Saavedra Nieto, Director y asistido por el Ing. David Pérez-Negron Rocha, fundador. - - -Se informa que se encuentra reunido el Quórum necesario para dar comienzo a la Asamblea con la presencia de la totalidad de sus asociados, según Planilla de Asistencia. - -Acto seguido se pasa a tratar el **PRIMER PUNTO** del orden del día. -Luego de la lectura del Informe de Actividades Realizadas y después de un breve intercambio de opiniones se aprueba que los asociados designados para suscribir la presente Acta sean Luis Alberto Saavedra Nieto y David Perez-Negron Rocha. - - -A continuación se trata el **SEGUNDO PUNTO** del Orden del Día. -Luego de la escucha del Reporte un breve diálogo al respecto, la votación dio como resultado la **APROBACIÓN POR UNANIMIDAD** el Balance Anual tratado; quedando de esta manera aprobado por mayoría el Balance Anual. - -Se pasa a tratar el **TERCER PUNTO** del Orden del Día. -Habiendo presentación de las Listas, se procede a su lectura. Luego de un breve debate e intercambio de opiniones entre los asociados, la votación dio como resultado la APROBACIÓN POR UNANIMIDAD de la Lista, se proponen como candidatos a las siguientes personas: - -**Para el cargo de Secretario: el C. Alfonso Núñez Navarro -Para el cargo de Tesorero: el C. Omar Octavio Huerta Valdez** - -Los asociados propuestos como candidatos aceptan su candidatura. - -Luego de un breve debate e intercambio de opiniones la votación dio como resultado: - -Para el cargo de Secretario, **4 votos para Alfonso Núñez Navarro, 1 abstención, 0 votos en contra.** - -Se pasa a tratar el **PUNTO CUATRO** del Orden del Día. -Habiendo presentación de Listas, se procede a su lectura. Luego de un breve debate e intercambio de opiniones la votación dio como resultado la APROBACIÓN POR UNANIMIDAD de la Lista. Luego de un breve debate e intercambio de opiniones la votación dio como resultado: - -Para el cargo de Tesorero, **4 votos para Omar Octavio Huerta Valdez, 1 abstención, 0 votos en contra.** - -Finalmente se trata el **PUNTO QUINTO** del Orden del Día. - - -Los asociados propuestos como candidatos aceptan el cargo para el que fueron electos. - -De esta forma se proclaman a las autoridades electas por el término de 5 años y contar desde el día de la fecha, con la siguiente conformación: - - -**Del Consejo General** -Secretario: Alfonso Núñez Navarro -Tesorero: Octavio Huerta Valdez - - -Todos los candidatos electos aceptan expresamente el cargo. - -Siendo las 19:00 horas del día 31 de Diciembre del 2022 y habiéndose cumplimentado en su totalidad el Orden del Día previsto, se da por finalizada la Asamblea General. Se da lectura a esta Acta, y firman de conformidad los asambleístas designados, conjuntamente con el Presidente y el Consejo General, en el lugar y fecha indicados al inicio. - - - -## **Firmas de los Presentes** - -![](https://imgur.com/9ghNAPk.png) -______________________ -Luis Alberto Saavedra Nieto. - -![](https://i.imgur.com/xertJxD.png) -______________________ -David E. Pérez Negron R. - -![](https://i.imgur.com/8TbRdeR.png) -______________________ -Omar Octavio Huerta Valdez. - -![](https://i.imgur.com/UQD4ehY.png) -______________________ -Christian Sandoval. - -![](https://i.imgur.com/0LGljhs.png) -______________________ -Alfonso Nuñez. - -![](https://i.imgur.com/mXssbm0.png) -______________________ -Marco Blanke. - - diff --git a/assets/eip-1/EIP-process-update.jpg b/assets/eip-1/EIP-process-update.jpg deleted file mode 100644 index 6bc35d3..0000000 Binary files a/assets/eip-1/EIP-process-update.jpg and /dev/null differ diff --git a/assets/eip-1/EIP-process.png b/assets/eip-1/EIP-process.png deleted file mode 100644 index e3d7a60..0000000 Binary files a/assets/eip-1/EIP-process.png and /dev/null differ diff --git a/assets/eip-1/process.png b/assets/eip-1/process.png deleted file mode 100644 index d259bb9..0000000 Binary files a/assets/eip-1/process.png and /dev/null differ diff --git a/assets/eip-107/authorization-locked.png b/assets/eip-107/authorization-locked.png deleted file mode 100644 index 4f881bc..0000000 Binary files a/assets/eip-107/authorization-locked.png and /dev/null differ diff --git a/assets/eip-107/authorization-password.png b/assets/eip-107/authorization-password.png deleted file mode 100644 index 32b9119..0000000 Binary files a/assets/eip-107/authorization-password.png and /dev/null differ diff --git a/assets/eip-107/authorization.png b/assets/eip-107/authorization.png deleted file mode 100644 index b7dc9ae..0000000 Binary files a/assets/eip-107/authorization.png and /dev/null differ diff --git a/assets/eip-1175/wallet.png b/assets/eip-1175/wallet.png deleted file mode 100644 index fa58376..0000000 Binary files a/assets/eip-1175/wallet.png and /dev/null differ diff --git a/assets/eip-1175/workflow.png b/assets/eip-1175/workflow.png deleted file mode 100644 index 2b552ba..0000000 Binary files a/assets/eip-1175/workflow.png and /dev/null differ diff --git a/assets/eip-1207/rationale.png b/assets/eip-1207/rationale.png deleted file mode 100644 index 1f698d8..0000000 Binary files a/assets/eip-1207/rationale.png and /dev/null differ diff --git a/assets/eip-1283/state.png b/assets/eip-1283/state.png deleted file mode 100644 index 1737d25..0000000 Binary files a/assets/eip-1283/state.png and /dev/null differ diff --git a/assets/eip-1438/avatar.png b/assets/eip-1438/avatar.png deleted file mode 100644 index 4420431..0000000 Binary files a/assets/eip-1438/avatar.png and /dev/null differ diff --git a/assets/eip-1438/intro.png b/assets/eip-1438/intro.png deleted file mode 100644 index 68ceaa6..0000000 Binary files a/assets/eip-1438/intro.png and /dev/null differ diff --git a/assets/eip-1438/wallet.png b/assets/eip-1438/wallet.png deleted file mode 100644 index 735c3f1..0000000 Binary files a/assets/eip-1438/wallet.png and /dev/null differ diff --git a/assets/eip-1613/sequence.png b/assets/eip-1613/sequence.png deleted file mode 100644 index 2c54c37..0000000 Binary files a/assets/eip-1613/sequence.png and /dev/null differ diff --git a/assets/eip-1822/proxy-diagram.png b/assets/eip-1822/proxy-diagram.png deleted file mode 100644 index 26747b2..0000000 Binary files a/assets/eip-1822/proxy-diagram.png and /dev/null differ diff --git a/assets/eip-1884/BALANCE-run3.png b/assets/eip-1884/BALANCE-run3.png deleted file mode 100644 index 324647a..0000000 Binary files a/assets/eip-1884/BALANCE-run3.png and /dev/null differ diff --git a/assets/eip-1884/SLOAD-run3.png b/assets/eip-1884/SLOAD-run3.png deleted file mode 100644 index 674eb2d..0000000 Binary files a/assets/eip-1884/SLOAD-run3.png and /dev/null differ diff --git a/assets/eip-1884/geth_processing.png b/assets/eip-1884/geth_processing.png deleted file mode 100644 index 64bf92e..0000000 Binary files a/assets/eip-1884/geth_processing.png and /dev/null differ diff --git a/assets/eip-1884/run3.total-bars-5.png b/assets/eip-1884/run3.total-bars-5.png deleted file mode 100644 index 9eeb4b9..0000000 Binary files a/assets/eip-1884/run3.total-bars-5.png and /dev/null differ diff --git a/assets/eip-1884/run3.total-bars-6.png b/assets/eip-1884/run3.total-bars-6.png deleted file mode 100644 index 56548b9..0000000 Binary files a/assets/eip-1884/run3.total-bars-6.png and /dev/null differ diff --git a/assets/eip-1901/OpenRPC_structure.png b/assets/eip-1901/OpenRPC_structure.png deleted file mode 100644 index 412bb8a..0000000 Binary files a/assets/eip-1901/OpenRPC_structure.png and /dev/null differ diff --git a/assets/eip-1901/eth-generated-client-openrpc.gif b/assets/eip-1901/eth-generated-client-openrpc.gif deleted file mode 100644 index e3601be..0000000 Binary files a/assets/eip-1901/eth-generated-client-openrpc.gif and /dev/null differ diff --git a/assets/eip-1901/eth-playground-openrpc.gif b/assets/eip-1901/eth-playground-openrpc.gif deleted file mode 100644 index c095ef9..0000000 Binary files a/assets/eip-1901/eth-playground-openrpc.gif and /dev/null differ diff --git a/assets/eip-1901/inspector-mock-server-openrpc.png b/assets/eip-1901/inspector-mock-server-openrpc.png deleted file mode 100644 index 2e50ea1..0000000 Binary files a/assets/eip-1901/inspector-mock-server-openrpc.png and /dev/null differ diff --git a/assets/eip-1901/multi-geth-use-case.png b/assets/eip-1901/multi-geth-use-case.png deleted file mode 100644 index 08f5db4..0000000 Binary files a/assets/eip-1901/multi-geth-use-case.png and /dev/null differ diff --git a/assets/eip-1967/Sample-proxy-on-etherscan.png b/assets/eip-1967/Sample-proxy-on-etherscan.png deleted file mode 100644 index 7239b4f..0000000 Binary files a/assets/eip-1967/Sample-proxy-on-etherscan.png and /dev/null differ diff --git a/assets/eip-2025/block_rewards_distribution.png b/assets/eip-2025/block_rewards_distribution.png deleted file mode 100644 index 0ce3019..0000000 Binary files a/assets/eip-2025/block_rewards_distribution.png and /dev/null differ diff --git a/assets/eip-2025/loan_state.png b/assets/eip-2025/loan_state.png deleted file mode 100644 index 18a2b81..0000000 Binary files a/assets/eip-2025/loan_state.png and /dev/null differ diff --git a/assets/eip-2255/facebook_permissions.png b/assets/eip-2255/facebook_permissions.png deleted file mode 100644 index d08c73a..0000000 Binary files a/assets/eip-2255/facebook_permissions.png and /dev/null differ diff --git a/assets/eip-2255/log_in_with_apple.jpeg b/assets/eip-2255/log_in_with_apple.jpeg deleted file mode 100644 index d4c86e8..0000000 Binary files a/assets/eip-2255/log_in_with_apple.jpeg and /dev/null differ diff --git a/assets/eip-2255/permissions.png b/assets/eip-2255/permissions.png deleted file mode 100644 index c500977..0000000 Binary files a/assets/eip-2255/permissions.png and /dev/null differ diff --git a/assets/eip-2255/permissions_adventure.gif b/assets/eip-2255/permissions_adventure.gif deleted file mode 100644 index 811e44c..0000000 Binary files a/assets/eip-2255/permissions_adventure.gif and /dev/null differ diff --git a/assets/eip-2266/Example.sol b/assets/eip-2266/Example.sol deleted file mode 100644 index 4b0563c..0000000 --- a/assets/eip-2266/Example.sol +++ /dev/null @@ -1,455 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Copyright (c) 2019 Chris Haoyu LIN, Runchao HAN, Jiangshan YU -// ERC2266 is compatible with ERC20 standard: https://theethereum.wiki/w/index.php/ERC20_Token_Standard -// naming style follows the guide: https://solidity.readthedocs.io/en/v0.5.11/style-guide.html#naming-styles - -pragma solidity ^0.5.11; - -contract ERC20 { - function totalSupply() public view returns (uint); - function balanceOf(address tokenOwner) public view returns (uint balance); - function allowance(address tokenOwner, address spender) public view returns (uint remaining); - function transfer(address to, uint tokens) public returns (bool success); - function approve(address spender, uint tokens) public returns (bool success); - function transferFrom(address from, address to, uint tokens) public returns (bool success); - event Transfer(address indexed from, address indexed to, uint tokens); - event Approval(address indexed tokenOwner, address indexed spender, uint tokens); -} - -contract Example -{ - enum AssetState { Empty, Filled, Redeemed, Refunded } - - struct Swap { - bytes32 secretHash; - bytes32 secret; - address payable initiator; - address payable participant; - address tokenA; - address tokenB; - } - - struct InitiatorAsset { - uint256 amount; - uint256 refundTimestamp; - AssetState state; - } - - struct ParticipantAsset { - uint256 amount; - uint256 refundTimestamp; - AssetState state; - } - - struct Premium { - uint256 amount; - uint256 refundTimestamp; - AssetState state; - } - - mapping(bytes32 => Swap) public swap; - mapping(bytes32 => InitiatorAsset) public initiatorAsset; - mapping(bytes32 => ParticipantAsset) public participantAsset; - mapping(bytes32 => Premium) public premium; - - event SetUp( - bytes32 secretHash, - address initiator, - address participant, - address tokenA, - address tokenB, - uint256 initiatorAssetAmount, - uint256 participantAssetAmount, - uint256 premiumAmount - ); - - event Initiated( - uint256 initiateTimestamp, - bytes32 secretHash, - address initiator, - address participant, - address initiatorAssetToken, - uint256 initiatorAssetAmount, - uint256 initiatorAssetRefundTimestamp - ); - - event PremiumFilled( - uint256 fillPremiumTimestamp, - bytes32 secretHash, - address initiator, - address participant, - address premiumToken, - uint256 premiumAmount, - uint256 premiumRefundTimestamp - ); - - event Participated( - uint256 participateTimestamp, - bytes32 secretHash, - address initiator, - address participant, - address participantAssetToken, - uint256 participantAssetAmount, - uint256 participantAssetRefundTimestamp - ); - - event InitiatorAssetRedeemed( - uint256 redeemTimestamp, - bytes32 secretHash, - bytes32 secret, - address redeemer, - address assetToken, - uint256 amount - ); - - event ParticipantAssetRedeemed( - uint256 redeemTimestamp, - bytes32 secretHash, - bytes32 secret, - address redeemer, - address assetToken, - uint256 amount - ); - - event InitiatorAssetRefunded( - uint256 refundTimestamp, - bytes32 secretHash, - address refunder, - address assetToken, - uint256 amount - ); - - event ParticipantAssetRefunded( - uint256 refundTimestamp, - bytes32 secretHash, - address refunder, - address assetToken, - uint256 amount - ); - - event PremiumRedeemed( - uint256 redeemTimestamp, - bytes32 secretHash, - address redeemer, - address token, - uint256 amount - ); - - event PremiumRefunded( - uint256 refundTimestamp, - bytes32 secretHash, - address refunder, - address token, - uint256 amount - ); - - constructor() public {} - - modifier isInitiatorAssetEmptyState(bytes32 secretHash) { - require(initiatorAsset[secretHash].state == AssetState.Empty); - _; - } - - modifier isParticipantAssetEmptyState(bytes32 secretHash) { - require(participantAsset[secretHash].state == AssetState.Empty); - _; - } - - modifier isPremiumEmptyState(bytes32 secretHash) { - require(premium[secretHash].state == AssetState.Empty); - _; - } - - modifier canSetup(bytes32 secretHash) { - require(initiatorAsset[secretHash].state == AssetState.Empty); - require(participantAsset[secretHash].state == AssetState.Empty); - require(premium[secretHash].state == AssetState.Empty); - _; - } - - modifier canInitiate(bytes32 secretHash) { - require(swap[secretHash].initiator == msg.sender); - require(initiatorAsset[secretHash].state == AssetState.Empty); - require(ERC20(swap[secretHash].tokenA).balanceOf(msg.sender) >= initiatorAsset[secretHash].amount); - _; - } - - modifier canFillPremium(bytes32 secretHash) { - require(swap[secretHash].initiator == msg.sender); - require(premium[secretHash].state == AssetState.Empty); - require(ERC20(swap[secretHash].tokenB).balanceOf(msg.sender) >= premium[secretHash].amount); - _; - } - - modifier canParticipate(bytes32 secretHash) { - require(swap[secretHash].participant == msg.sender); - require(participantAsset[secretHash].state == AssetState.Empty); - require(premium[secretHash].state == AssetState.Filled); - require(ERC20(swap[secretHash].tokenB).balanceOf(msg.sender) >= participantAsset[secretHash].amount); - _; - } - - modifier checkRefundTimestampOverflow(uint256 refundTime) { - uint256 refundTimestamp = block.timestamp + refundTime; - require(refundTimestamp > block.timestamp, "calc refundTimestamp overflow"); - require(refundTimestamp > refundTime, "calc refundTimestamp overflow"); - _; - } - - modifier isAssetRedeemable(bytes32 secretHash, bytes32 secret) { - if (swap[secretHash].initiator == msg.sender) { - require(initiatorAsset[secretHash].state == AssetState.Filled); - require(block.timestamp <= initiatorAsset[secretHash].refundTimestamp); - } else { - require(swap[secretHash].participant == msg.sender); - require(participantAsset[secretHash].state == AssetState.Filled); - require(block.timestamp <= participantAsset[secretHash].refundTimestamp); - } - require(sha256(abi.encodePacked(secret)) == secretHash); - _; - } - - modifier isAssetRefundable(bytes32 secretHash) { - if (swap[secretHash].initiator == msg.sender) { - require(initiatorAsset[secretHash].state == AssetState.Filled); - require(block.timestamp > initiatorAsset[secretHash].refundTimestamp); - } else { - require(swap[secretHash].participant == msg.sender); - require(participantAsset[secretHash].state == AssetState.Filled); - require(block.timestamp > participantAsset[secretHash].refundTimestamp); - } - _; - } - - modifier isPremiumFilledState(bytes32 secretHash) { - require(premium[secretHash].state == AssetState.Filled); - _; - } - - // Premium is redeemable for Bob if Bob participates and redeem - // before premium's timelock expires - modifier isPremiumRedeemable(bytes32 secretHash) { - // the participant invokes this method to redeem the premium - require(swap[secretHash].participant == msg.sender); - // the premium should be deposited - require(premium[secretHash].state == AssetState.Filled); - // if Bob participates, which means participantAsset will be: Filled -> (Redeemed/Refunded) - require(participantAsset[secretHash].state == AssetState.Refunded || participantAsset[secretHash].state == AssetState.Redeemed); - // the premium timelock should not be expired - require(block.timestamp <= premium[secretHash].refundTimestamp); - _; - } - - // Premium is refundable for Alice only when Alice initiates - // but Bob does not participate after premium's timelock expires - modifier isPremiumRefundable(bytes32 secretHash) { - // the initiator invokes this method to refund the premium - require(swap[secretHash].initiator == msg.sender); - // the premium should be deposited - require(premium[secretHash].state == AssetState.Filled); - // asset2 should be empty - // which means Bob does not participate - require(participantAsset[secretHash].state == AssetState.Empty); - require(block.timestamp > premium[secretHash].refundTimestamp); - _; - } - - function setup(bytes32 secretHash, - address payable initiator, - address tokenA, - address tokenB, - uint256 initiatorAssetAmount, - address payable participant, - uint256 participantAssetAmount, - uint256 premiumAmount) - public - payable - canSetup(secretHash) - { - swap[secretHash].secretHash = secretHash; - swap[secretHash].initiator = initiator; - swap[secretHash].participant = participant; - swap[secretHash].tokenA = tokenA; - swap[secretHash].tokenB = tokenB; - initiatorAsset[secretHash].amount = initiatorAssetAmount; - initiatorAsset[secretHash].state = AssetState.Empty; - participantAsset[secretHash].amount = participantAssetAmount; - participantAsset[secretHash].state = AssetState.Empty; - premium[secretHash].amount = premiumAmount; - premium[secretHash].state = AssetState.Empty; - - emit SetUp( - secretHash, - initiator, - participant, - tokenA, - tokenB, - initiatorAssetAmount, - participantAssetAmount, - premiumAmount - ); - } - - // Initiator needs to pay for the initiatorAsset(tokenA) with initiatorAssetAmount - // Initiator will also need to call tokenA.approve(this_contract_address, initiatorAssetAmount) in advance - function initiate(bytes32 secretHash, uint256 assetRefundTime) - public - payable - canInitiate(secretHash) - checkRefundTimestampOverflow(assetRefundTime) - { - ERC20(swap[secretHash].tokenA).transferFrom(swap[secretHash].initiator, address(this), initiatorAsset[secretHash].amount); - initiatorAsset[secretHash].state = AssetState.Filled; - initiatorAsset[secretHash].refundTimestamp = block.timestamp + assetRefundTime; - - emit Initiated( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].participant, - swap[secretHash].tokenA, - initiatorAsset[secretHash].amount, - initiatorAsset[secretHash].refundTimestamp - ); - } - - // Initiator needs to pay for the premium(tokenB) with premiumAmount - // Initiator will also need to call tokenB.approve(this_contract_address, premiumAmount) in advance - function fillPremium(bytes32 secretHash, uint256 premiumRefundTime) - public - payable - canFillPremium(secretHash) - checkRefundTimestampOverflow(premiumRefundTime) - { - ERC20(swap[secretHash].tokenB).transferFrom(swap[secretHash].initiator, address(this), premium[secretHash].amount); - premium[secretHash].state = AssetState.Filled; - premium[secretHash].refundTimestamp = block.timestamp + premiumRefundTime; - - emit PremiumFilled( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].participant, - swap[secretHash].tokenB, - premium[secretHash].amount, - premium[secretHash].refundTimestamp - ); - } - - // Participant needs to pay for the participantAsset(tokenB) with participantAssetAmount - // Participant will also need to call tokenB.approve(this_contract_address, participantAssetAmount) in advance - function participate(bytes32 secretHash, uint256 assetRefundTime) - public - payable - canParticipate(secretHash) - checkRefundTimestampOverflow(assetRefundTime) - { - ERC20(swap[secretHash].tokenB).transferFrom(swap[secretHash].participant, address(this), participantAsset[secretHash].amount); - participantAsset[secretHash].state = AssetState.Filled; - participantAsset[secretHash].refundTimestamp = block.timestamp + assetRefundTime; - - emit Participated( - block.timestamp, - secretHash, - swap[secretHash].initiator, - msg.sender, - swap[secretHash].tokenB, - participantAsset[secretHash].amount, - participantAsset[secretHash].refundTimestamp - ); - } - - function redeemAsset(bytes32 secret, bytes32 secretHash) - public - isAssetRedeemable(secretHash, secret) - { - swap[secretHash].secret = secret; - if (swap[secretHash].initiator == msg.sender) { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, participantAsset[secretHash].amount); - participantAsset[secretHash].state = AssetState.Redeemed; - - emit ParticipantAssetRedeemed( - block.timestamp, - secretHash, - secret, - msg.sender, - swap[secretHash].tokenB, - participantAsset[secretHash].amount - ); - } else { - ERC20(swap[secretHash].tokenA).transfer(msg.sender, initiatorAsset[secretHash].amount); - initiatorAsset[secretHash].state = AssetState.Redeemed; - - emit InitiatorAssetRedeemed( - block.timestamp, - secretHash, - secret, - msg.sender, - swap[secretHash].tokenA, - initiatorAsset[secretHash].amount - ); - } - } - - function refundAsset(bytes32 secretHash) - public - isPremiumFilledState(secretHash) - isAssetRefundable(secretHash) - { - if (swap[secretHash].initiator == msg.sender) { - ERC20(swap[secretHash].tokenA).transfer(msg.sender, initiatorAsset[secretHash].amount); - initiatorAsset[secretHash].state = AssetState.Refunded; - - emit InitiatorAssetRefunded( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].tokenA, - initiatorAsset[secretHash].amount - ); - } else { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, participantAsset[secretHash].amount); - participantAsset[secretHash].state = AssetState.Refunded; - - emit ParticipantAssetRefunded( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].tokenB, - participantAsset[secretHash].amount - ); - } - } - - function redeemPremium(bytes32 secretHash) - public - isPremiumRedeemable(secretHash) - { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, premium[secretHash].amount); - premium[secretHash].state = AssetState.Redeemed; - - emit PremiumRefunded( - block.timestamp, - swap[secretHash].secretHash, - msg.sender, - swap[secretHash].tokenB, - premium[secretHash].amount - ); - } - - function refundPremium(bytes32 secretHash) - public - isPremiumRefundable(secretHash) - { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, premium[secretHash].amount); - premium[secretHash].state = AssetState.Refunded; - - emit PremiumRefunded( - block.timestamp, - swap[secretHash].secretHash, - msg.sender, - swap[secretHash].tokenB, - premium[secretHash].amount - ); - } -} diff --git a/assets/eip-2330/Extsload.sol b/assets/eip-2330/Extsload.sol deleted file mode 100644 index dfbffe6..0000000 --- a/assets/eip-2330/Extsload.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -abstract contract Extsload { - function extsload(bytes32[] memory slots) - external - view - returns (bytes32[] memory) - { - for (uint256 i; i < slots.length; i++) { - bytes32 slot = slots[i]; - bytes32 val; - assembly { - val := sload(slot) - } - slots[i] = val; - } - return slots; - } -} - -// A contract who can make their state publicly accessible without EIP-2330 -contract DeFiProtocol is Extsload { - // code -} diff --git a/assets/eip-2535/Contributors.md b/assets/eip-2535/Contributors.md deleted file mode 100644 index de9097e..0000000 --- a/assets/eip-2535/Contributors.md +++ /dev/null @@ -1,79 +0,0 @@ -## Contributors - -* Andrew Redden (@androolloyd) -* Patrick Gallagher (@pi0neerpat) -* Leo Alt (@leonardoalt) -* Santiago Palladino (@spalladino) -* William Entriken (@fulldecent) -* Gonçalo Sá (@GNSPS) -* Brian Burns (@Droopy78) -* Ramesh Nair(@hiddentao) -* Jules Goddard (@JulesGoddard) -* Micah Zoltu (@MicahZoltu) -* Sam Wilson (@SamWilsn) -* William Morriss (@wjmelements) -* Zachary (@Remscar) -* Patrick Collins (@PatrickAlphaC) -* Hadrien Croubois (@Amxx) -* (@farreldarian) -* Kelvin Schoofs (@SchoofsKelvin) -* (@0xpApaSmURf) -* Nathan Sala (@nataouze) -* Anders Torbjornsen (@anders-torbjornsen) -* (@Pandapip1) -* Xavier Iturralde (@xibot) -* Coder Dan (@cinnabarhorse) -* GldnXross (@gldnxross) -* Christian Reitwiessner (@chriseth) -* Timidan (@Timidan) -* cyotee doge (@cyotee) -* Glory Praise Emmanuel (@emmaglorypraise) -* Ed Zynda (@ezynda3) -* Arthur Nesbitt (@nesbitta) -* Cliff Hall (@cliffhall) -* Tyler Scott Ward (@tylerscottward) -* Troy Murray (@DannyDesert) -* Dan Finlay (@danfinlay) -* Theodore Georgas (@tgeorgas) -* Aditya Palepu (@apalepu23) -* Ronan Sandford (@wighawag) -* Markus Waas (@gorgos) -* Blessing Emah (@BlessingEmah) -* Andrew Edwards -* Ashwin Yardi (@ashwinYardi) -* Marco Castignoli (@marcocastignoli) -* Blaine Bublitz (@phated) -* Bearded -* Nick Barry (@ItsNickBarry) -* (@Vectorized) -* Rachit Srivastava (@rachit2501) -* Neeraj Kashyap (@zomglings) -* Zac Denham (@zdenham) -* JA (@ubinatus) -* Carter Carlson (@cartercarlson) -* James Sayer (@jamessayer98) -* Arpit Temani (@temaniarpit27) -* Parv Garg (@parv3213) -* Publius (@publiuss) -* Guy Hance (@guyhance) -* Payn (@Ayuilos) -* Luis Schliesske (@gitpusha) -* Hilmar Orth (@hilmarx) -* Matthieu Marie Joseph (@Gauddel) -* David Uzochukwu (@davidpius95) -* TJ VanSlooten (@tjvsx) -* 0xFluffyBeard (@0xFluffyBeard) -* Florian Pfeiffer (@FlorianPfeifferKanaloaNetwork) -* Mick de Graaf(@MickdeGraaf) -* Alessio Delmonti (@Alexintosh) -* Neirenoir (@Neirenoir) -* Evert Kors (@Evert0x) -* Patrick Kim (@pakim249CAL) -* Ersan YAKIT (@ersanyakit) -* Matias Arazi (@MatiArazi) -* Lucas Grasso Ramos (@LucasGrasso) -* Nikolay Angelov (@NikolayAngelov) -* John Reynolds (@gweiworld) -* Viraz Malhotra (@viraj124) -* Kemal Emre Ballı (@emrbli) -* Zack Peng (@zackpeng) diff --git a/assets/eip-2535/DiamondDiagram.png b/assets/eip-2535/DiamondDiagram.png deleted file mode 100644 index 43e49cc..0000000 Binary files a/assets/eip-2535/DiamondDiagram.png and /dev/null differ diff --git a/assets/eip-2535/diamond.svg b/assets/eip-2535/diamond.svg deleted file mode 100644 index a288057..0000000 --- a/assets/eip-2535/diamond.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/eip-2535/diamondstorage1.png b/assets/eip-2535/diamondstorage1.png deleted file mode 100644 index 3889d62..0000000 Binary files a/assets/eip-2535/diamondstorage1.png and /dev/null differ diff --git a/assets/eip-2535/facetreuse.png b/assets/eip-2535/facetreuse.png deleted file mode 100644 index 481145e..0000000 Binary files a/assets/eip-2535/facetreuse.png and /dev/null differ diff --git a/assets/eip-2535/reference/Diamond.sol b/assets/eip-2535/reference/Diamond.sol deleted file mode 100644 index f382587..0000000 --- a/assets/eip-2535/reference/Diamond.sol +++ /dev/null @@ -1,589 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/******************************************************************************\ -* Author: Nick Mudge , Twitter/Github: @mudgen -* EIP-2535 Diamonds -/******************************************************************************/ - -// NOTE: -// To see the various things in this file in their proper directory structure -// please download the zip archive version of this reference implementation. -// The zip archive also includes a deployment script and tests. - -interface IDiamond { - enum FacetCutAction {Add, Replace, Remove} - // Add=0, Replace=1, Remove=2 - - struct FacetCut { - address facetAddress; - FacetCutAction action; - bytes4[] functionSelectors; - } - - event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); -} - -interface IDiamondCut is IDiamond { - /// @notice Add/replace/remove any number of functions and optionally execute - /// a function with delegatecall - /// @param _diamondCut Contains the facet addresses and function selectors - /// @param _init The address of the contract or facet to execute _calldata - /// @param _calldata A function call, including function selector and arguments - /// _calldata is executed with delegatecall on _init - function diamondCut( - FacetCut[] calldata _diamondCut, - address _init, - bytes calldata _calldata - ) external; -} - -/////////////////////////////////////////////// -// LibDiamond -// LibDiamond defines the diamond storage that is used by this reference -// implementation. -// LibDiamond contains internal functions and no external functions. -// LibDiamond internal functions are used by DiamondCutFacet, -// DiamondLoupeFacet and the diamond proxy contract (the Diamond contract). - -error NoSelectorsGivenToAdd(); -error NotContractOwner(address _user, address _contractOwner); -error NoSelectorsProvidedForFacetForCut(address _facetAddress); -error CannotAddSelectorsToZeroAddress(bytes4[] _selectors); -error NoBytecodeAtAddress(address _contractAddress, string _message); -error IncorrectFacetCutAction(uint8 _action); -error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); -error CannotReplaceFunctionsFromFacetWithZeroAddress(bytes4[] _selectors); -error CannotReplaceImmutableFunction(bytes4 _selector); -error CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet(bytes4 _selector); -error CannotReplaceFunctionThatDoesNotExists(bytes4 _selector); -error RemoveFacetAddressMustBeZeroAddress(address _facetAddress); -error CannotRemoveFunctionThatDoesNotExist(bytes4 _selector); -error CannotRemoveImmutableFunction(bytes4 _selector); -error InitializationFunctionReverted(address _initializationContractAddress, bytes _calldata); - -library LibDiamond { - bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); - - struct FacetAddressAndSelectorPosition { - address facetAddress; - uint16 selectorPosition; - } - - struct DiamondStorage { - // function selector => facet address and selector position in selectors array - mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition; - bytes4[] selectors; - mapping(bytes4 => bool) supportedInterfaces; - // owner of the contract - address contractOwner; - } - - function diamondStorage() internal pure returns (DiamondStorage storage ds) { - bytes32 position = DIAMOND_STORAGE_POSITION; - assembly { - ds.slot := position - } - } - - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - function setContractOwner(address _newOwner) internal { - DiamondStorage storage ds = diamondStorage(); - address previousOwner = ds.contractOwner; - ds.contractOwner = _newOwner; - emit OwnershipTransferred(previousOwner, _newOwner); - } - - function contractOwner() internal view returns (address contractOwner_) { - contractOwner_ = diamondStorage().contractOwner; - } - - function enforceIsContractOwner() internal view { - if(msg.sender != diamondStorage().contractOwner) { - revert NotContractOwner(msg.sender, diamondStorage().contractOwner); - } - } - - event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); - - // Internal function version of diamondCut - function diamondCut( - IDiamondCut.FacetCut[] memory _diamondCut, - address _init, - bytes memory _calldata - ) internal { - for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { - bytes4[] memory functionSelectors = _diamondCut[facetIndex].functionSelectors; - address facetAddress = _diamondCut[facetIndex].facetAddress; - if(functionSelectors.length == 0) { - revert NoSelectorsProvidedForFacetForCut(facetAddress); - } - IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; - if (action == IDiamond.FacetCutAction.Add) { - addFunctions(facetAddress, functionSelectors); - } else if (action == IDiamond.FacetCutAction.Replace) { - replaceFunctions(facetAddress, functionSelectors); - } else if (action == IDiamond.FacetCutAction.Remove) { - removeFunctions(facetAddress, functionSelectors); - } else { - revert IncorrectFacetCutAction(uint8(action)); - } - } - emit DiamondCut(_diamondCut, _init, _calldata); - initializeDiamondCut(_init, _calldata); - } - - function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { - if(_facetAddress == address(0)) { - revert CannotAddSelectorsToZeroAddress(_functionSelectors); - } - DiamondStorage storage ds = diamondStorage(); - uint16 selectorCount = uint16(ds.selectors.length); - enforceHasContractCode(_facetAddress, "LibDiamondCut: Add facet has no code"); - for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { - bytes4 selector = _functionSelectors[selectorIndex]; - address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress; - if(oldFacetAddress != address(0)) { - revert CannotAddFunctionToDiamondThatAlreadyExists(selector); - } - ds.facetAddressAndSelectorPosition[selector] = FacetAddressAndSelectorPosition(_facetAddress, selectorCount); - ds.selectors.push(selector); - selectorCount++; - } - } - - function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { - DiamondStorage storage ds = diamondStorage(); - if(_facetAddress == address(0)) { - revert CannotReplaceFunctionsFromFacetWithZeroAddress(_functionSelectors); - } - enforceHasContractCode(_facetAddress, "LibDiamondCut: Replace facet has no code"); - for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { - bytes4 selector = _functionSelectors[selectorIndex]; - address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress; - // can't replace immutable functions -- functions defined directly in the diamond in this case - if(oldFacetAddress == address(this)) { - revert CannotReplaceImmutableFunction(selector); - } - if(oldFacetAddress == _facetAddress) { - revert CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet(selector); - } - if(oldFacetAddress == address(0)) { - revert CannotReplaceFunctionThatDoesNotExists(selector); - } - // replace old facet address - ds.facetAddressAndSelectorPosition[selector].facetAddress = _facetAddress; - } - } - - function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { - DiamondStorage storage ds = diamondStorage(); - uint256 selectorCount = ds.selectors.length; - if(_facetAddress != address(0)) { - revert RemoveFacetAddressMustBeZeroAddress(_facetAddress); - } - for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { - bytes4 selector = _functionSelectors[selectorIndex]; - FacetAddressAndSelectorPosition memory oldFacetAddressAndSelectorPosition = ds.facetAddressAndSelectorPosition[selector]; - if(oldFacetAddressAndSelectorPosition.facetAddress == address(0)) { - revert CannotRemoveFunctionThatDoesNotExist(selector); - } - - - // can't remove immutable functions -- functions defined directly in the diamond - if(oldFacetAddressAndSelectorPosition.facetAddress == address(this)) { - revert CannotRemoveImmutableFunction(selector); - } - // replace selector with last selector - selectorCount--; - if (oldFacetAddressAndSelectorPosition.selectorPosition != selectorCount) { - bytes4 lastSelector = ds.selectors[selectorCount]; - ds.selectors[oldFacetAddressAndSelectorPosition.selectorPosition] = lastSelector; - ds.facetAddressAndSelectorPosition[lastSelector].selectorPosition = oldFacetAddressAndSelectorPosition.selectorPosition; - } - // delete last selector - ds.selectors.pop(); - delete ds.facetAddressAndSelectorPosition[selector]; - } - } - - function initializeDiamondCut(address _init, bytes memory _calldata) internal { - if (_init == address(0)) { - return; - } - enforceHasContractCode(_init, "LibDiamondCut: _init address has no code"); - (bool success, bytes memory error) = _init.delegatecall(_calldata); - if (!success) { - if (error.length > 0) { - // bubble up error - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(error) - revert(add(32, error), returndata_size) - } - } else { - revert InitializationFunctionReverted(_init, _calldata); - } - } - } - - function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { - uint256 contractSize; - assembly { - contractSize := extcodesize(_contract) - } - if(contractSize == 0) { - revert NoBytecodeAtAddress(_contract, _errorMessage); - } - } -} - -/////////////////////////////////////////////// -// These facets are added to the diamond. -/////////////////////////////////////////////// - -contract DiamondCutFacet is IDiamondCut { - /// @notice Add/replace/remove any number of functions and optionally execute - /// a function with delegatecall - /// @param _diamondCut Contains the facet addresses and function selectors - /// @param _init The address of the contract or facet to execute _calldata - /// @param _calldata A function call, including function selector and arguments - /// _calldata is executed with delegatecall on _init - function diamondCut( - FacetCut[] calldata _diamondCut, - address _init, - bytes calldata _calldata - ) external override { - LibDiamond.enforceIsContractOwner(); - LibDiamond.diamondCut(_diamondCut, _init, _calldata); - } -} - -// The functions in DiamondLoupeFacet MUST be added to a diamond. -// The EIP-2535 Diamond standard requires these functions. - -interface IERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceId The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -// A loupe is a small magnifying glass used to look at diamonds. -// These functions look at diamonds -interface IDiamondLoupe { - /// These functions are expected to be called frequently - /// by tools. - - struct Facet { - address facetAddress; - bytes4[] functionSelectors; - } - - /// @notice Gets all facet addresses and their four byte function selectors. - /// @return facets_ Facet - function facets() external view returns (Facet[] memory facets_); - - /// @notice Gets all the function selectors supported by a specific facet. - /// @param _facet The facet address. - /// @return facetFunctionSelectors_ - function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); - - /// @notice Get all the facet addresses used by a diamond. - /// @return facetAddresses_ - function facetAddresses() external view returns (address[] memory facetAddresses_); - - /// @notice Gets the facet that supports the given selector. - /// @dev If facet is not found return address(0). - /// @param _functionSelector The function selector. - /// @return facetAddress_ The facet address. - function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); -} - -contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { - // Diamond Loupe Functions - //////////////////////////////////////////////////////////////////// - /// These functions are expected to be called frequently by tools. - // - // struct Facet { - // address facetAddress; - // bytes4[] functionSelectors; - // } - /// @notice Gets all facets and their selectors. - /// @return facets_ Facet - function facets() external override view returns (Facet[] memory facets_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - uint256 selectorCount = ds.selectors.length; - // create an array set to the maximum size possible - facets_ = new Facet[](selectorCount); - // create an array for counting the number of selectors for each facet - uint16[] memory numFacetSelectors = new uint16[](selectorCount); - // total number of facets - uint256 numFacets; - // loop through function selectors - for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { - bytes4 selector = ds.selectors[selectorIndex]; - address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; - bool continueLoop = false; - // find the functionSelectors array for selector and add selector to it - for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { - if (facets_[facetIndex].facetAddress == facetAddress_) { - facets_[facetIndex].functionSelectors[numFacetSelectors[facetIndex]] = selector; - numFacetSelectors[facetIndex]++; - continueLoop = true; - break; - } - } - // if functionSelectors array exists for selector then continue loop - if (continueLoop) { - continueLoop = false; - continue; - } - // create a new functionSelectors array for selector - facets_[numFacets].facetAddress = facetAddress_; - facets_[numFacets].functionSelectors = new bytes4[](selectorCount); - facets_[numFacets].functionSelectors[0] = selector; - numFacetSelectors[numFacets] = 1; - numFacets++; - } - for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { - uint256 numSelectors = numFacetSelectors[facetIndex]; - bytes4[] memory selectors = facets_[facetIndex].functionSelectors; - // setting the number of selectors - assembly { - mstore(selectors, numSelectors) - } - } - // setting the number of facets - assembly { - mstore(facets_, numFacets) - } - } - - /// @notice Gets all the function selectors supported by a specific facet. - /// @param _facet The facet address. - /// @return _facetFunctionSelectors The selectors associated with a facet address. - function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory _facetFunctionSelectors) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - uint256 selectorCount = ds.selectors.length; - uint256 numSelectors; - _facetFunctionSelectors = new bytes4[](selectorCount); - // loop through function selectors - for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { - bytes4 selector = ds.selectors[selectorIndex]; - address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; - if (_facet == facetAddress_) { - _facetFunctionSelectors[numSelectors] = selector; - numSelectors++; - } - } - // Set the number of selectors in the array - assembly { - mstore(_facetFunctionSelectors, numSelectors) - } - } - - /// @notice Get all the facet addresses used by a diamond. - /// @return facetAddresses_ - function facetAddresses() external override view returns (address[] memory facetAddresses_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - uint256 selectorCount = ds.selectors.length; - // create an array set to the maximum size possible - facetAddresses_ = new address[](selectorCount); - uint256 numFacets; - // loop through function selectors - for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { - bytes4 selector = ds.selectors[selectorIndex]; - address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; - bool continueLoop = false; - // see if we have collected the address already and break out of loop if we have - for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { - if (facetAddress_ == facetAddresses_[facetIndex]) { - continueLoop = true; - break; - } - } - // continue loop if we already have the address - if (continueLoop) { - continueLoop = false; - continue; - } - // include address - facetAddresses_[numFacets] = facetAddress_; - numFacets++; - } - // Set the number of facet addresses in the array - assembly { - mstore(facetAddresses_, numFacets) - } - } - - /// @notice Gets the facet address that supports the given selector. - /// @dev If facet is not found return address(0). - /// @param _functionSelector The function selector. - /// @return facetAddress_ The facet address. - function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - facetAddress_ = ds.facetAddressAndSelectorPosition[_functionSelector].facetAddress; - } - - // This implements ERC-165. - function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - return ds.supportedInterfaces[_interfaceId]; - } -} - -/// @title ERC-173 Contract Ownership Standard -/// Note: the ERC-165 identifier for this interface is 0x7f5828d0 -/* is ERC165 */ -interface IERC173 { - /// @dev This emits when ownership of a contract changes. - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /// @notice Get the address of the owner - /// @return owner_ The address of the owner. - function owner() external view returns (address owner_); - - /// @notice Set the address of the new owner of the contract - /// @dev Set _newOwner to address(0) to renounce any ownership. - /// @param _newOwner The address of the new owner of the contract - function transferOwnership(address _newOwner) external; -} - -contract OwnershipFacet is IERC173 { - function transferOwnership(address _newOwner) external override { - LibDiamond.enforceIsContractOwner(); - LibDiamond.setContractOwner(_newOwner); - } - - function owner() external override view returns (address owner_) { - owner_ = LibDiamond.contractOwner(); - } -} -/////////////////////////////////////////////// - -/////////////////////////////////////////////// -// DiamondInit -// This contract and function are used to initialize state variables and/or do other actions -// when the `diamondCut` function is called. -// It is expected that this contract is customized if you want to deploy your diamond -// with data from a deployment script. Use the init function to initialize state variables -// of your diamond. Add parameters to the init funciton if you need to. -// DiamondInit can be used during deployment or for upgrades. - -// Adding parameters to the `init` or other functions you add here can make a single deployed -// DiamondInit contract reusable accross upgrades, and can be used for multiple diamonds. - -contract DiamondInit { - - // You can add parameters to this function in order to pass in - // data to set your own state variables - function init() external { - // adding ERC165 data - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - ds.supportedInterfaces[type(IERC165).interfaceId] = true; - ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; - ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; - ds.supportedInterfaces[type(IERC173).interfaceId] = true; - - // add your own state variables - // EIP-2535 specifies that the `diamondCut` function takes two optional - // arguments: address _init and bytes calldata _calldata - // These arguments are used to execute an arbitrary function using delegatecall - // in order to set state variables in the diamond during deployment or an upgrade - // More info in the EIP2535 Diamonds standard. - } -} - -/////////////////////////////////////////////// -// DiamondMultiInit -// This version of DiamondInit can be used to execute multiple initialization functions. -// It is expected that this contract is customized if you want to deploy or upgrade your diamond with it. - -error AddressAndCalldataLengthDoNotMatch(uint256 _addressesLength, uint256 _calldataLength); - -contract DiamondMultiInit { - - // This function is provided in the third parameter of the `diamondCut` function. - // The `diamondCut` function executes this function to execute multiple initializer functions for a single upgrade. - - function multiInit(address[] calldata _addresses, bytes[] calldata _calldata) external { - if(_addresses.length != _calldata.length) { - revert AddressAndCalldataLengthDoNotMatch(_addresses.length, _calldata.length); - } - for(uint i; i < _addresses.length; i++) { - LibDiamond.initializeDiamondCut(_addresses[i], _calldata[i]); - } - } -} - - -/////////////////////////////////////////////// -// Diamond -// The diamond proxy contract. - - -// When no function exists for function called -error FunctionNotFound(bytes4 _functionSelector); - -// This is used in diamond constructor -// more arguments are added to this struct -// this avoids stack too deep errors -struct DiamondArgs { - address owner; - address init; - bytes initCalldata; -} - -contract Diamond { - - // Remember to add the loupe functions from DiamondLoupeFacet to the diamond. - // The loupe functions are required by the EIP2535 Diamonds standard - - constructor(IDiamondCut.FacetCut[] memory _diamondCut, DiamondArgs memory _args) payable { - LibDiamond.setContractOwner(_args.owner); - LibDiamond.diamondCut(_diamondCut, _args.init, _args.initCalldata); - - // Code can be added here to perform actions and set state variables. - } - - // Find facet for function that is called and execute the - // function if a facet is found and return any value. - fallback() external payable { - LibDiamond.DiamondStorage storage ds; - bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; - // get diamond storage - assembly { - ds.slot := position - } - // get facet from function selector - address facet = ds.facetAddressAndSelectorPosition[msg.sig].facetAddress; - if(facet == address(0)) { - revert FunctionNotFound(msg.sig); - } - // Execute external function from facet using delegatecall and return any value. - assembly { - // copy function selector and any arguments - calldatacopy(0, 0, calldatasize()) - // execute function call using the facet - let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) - // get any return value - returndatacopy(0, 0, returndatasize()) - // return any return value or error back to the caller - switch result - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } - - receive() external payable {} -} diff --git a/assets/eip-2535/reference/EIP2535-Diamonds-Reference-Implementation.zip b/assets/eip-2535/reference/EIP2535-Diamonds-Reference-Implementation.zip deleted file mode 100644 index 9be228e..0000000 Binary files a/assets/eip-2535/reference/EIP2535-Diamonds-Reference-Implementation.zip and /dev/null differ diff --git a/assets/eip-2535/storage-examples/AppStorage.sol b/assets/eip-2535/storage-examples/AppStorage.sol deleted file mode 100644 index 69ca46e..0000000 --- a/assets/eip-2535/storage-examples/AppStorage.sol +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Diamond Storage is particularly good for isolating or compartmenting state variables to specific -// facets or functionality. This is great for creating modular facets that can be understood as their -// own units and be added to diamonds. A diamond with a lot of functionality is well organized and -// understandable if each of its facets can be understood in isolation. Diamond Storage helps make that -// possible. - -// However, you may want to share state variables specific to your application with facets that are specific -// to your application. It can get somewhat tedious to call a `diamondStorage()` function in every function -// that you want to access state variables. - -// `AppStorage` is a specialized version of Diamond Storage. It is a more convenient way to access -// application specific state variables that are shared among facets. - -// The pattern works in the following way: - -// 1. Define a struct called AppStorage that contains all the state variables specific to your application -// and that you plan to share with different facets. Store AppStorage in a file. Any of your facets can -// now import this file to access the state variables. - -struct AppStorage { - uint256 secondVar; - uint256 firstVar; - uint256 lastVar; - // add other state variables ... -} - - -// 2. In a facet that imports the AppStorage struct declare an AppStorage state variable called `s`. -// This should be the only state variable declared in the facet. - -// 3. In your facet you can now access all the state variables in AppStorage by prepending state variables -// with `s.`. Here is example code: - - -// import { AppStorage } from "./LibAppStorage.sol"; - -contract AFacet { - AppStorage internal s; - - function sumVariables() external { - s.lastVar = s.firstVar + s.secondVar; - } - - function getFirsVar() external view returns (uint256) { - return s.firstVar; - } - - function setLastVar(uint256 _newValue) external { - s.lastVar = _newValue; - } -} - -// Sharing AppStorage in another facet: - -// import { AppStorage } from "./LibAppStorage.sol"; - -contract SomeOtherFacet { - AppStorage internal s; - - function getLargerVar() external view returns (uint256) { - uint256 firstVar = s.firstVar; - uint256 secondVar = s.secondVar; - if(firstVar > secondVar) { - return firstVar; - } - else { - return secondVar; - } - } -} - -// Using the 's.' prefix to access AppStorage is a nice convention because it makes state variables -// concise, easy to access, and it distinguishes state variables from local variables and prevents -// name clashes/shadowing with local variables and function names. It helps identify and make -// explicit state variables in a convenient and concise way. AppStorage can be used in regualar -// contracts as well as proxy contracts, diamonds, implementation contracts, Solidity libraries and -// facets. - -// Since `AppStorage s` is the first and only state variable declared in facets its position in -// contract storage is `0`. This fact can be used to access AppStorage in Solidity libraries using -// diamond storage access. Here's an example of that: - -library LibAppStorage { - function appStorage() internal pure returns (AppStorage storage ds) { - assembly { ds.slot := 0 } - } - - function someFunction() internal { - AppStorage storage s = appStorage(); - s.firstVar = 8; - //... do more stuff - } -} - -// `AppStorage s` can be declared as the one and only state variable in facets or it can be declared in a -// contract that facets inherit. - -// AppStorage won't work if state variables are declared outside of AppStorage and outside of Diamond Storage. -// It is a common error for a facet to inherit a contract that declares state variables outside AppStorage and -// Diamond Storage. This causes a misalignment of state variables. - -// One downside is that state variables can't be declared public in structs so getter functions can't -// automatically be created this way. But it can be nice to make your own getter functions for -// state variables because it is explicit. - -// The rules for upgrading AppStorage are the same for Diamond Storage. These rules can be found at -// the end of the file ./DiamondStorage.sol \ No newline at end of file diff --git a/assets/eip-2535/storage-examples/DiamondStorage.sol b/assets/eip-2535/storage-examples/DiamondStorage.sol deleted file mode 100644 index b6fe022..0000000 --- a/assets/eip-2535/storage-examples/DiamondStorage.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Diamond storage is a contract storage strategy that is used in proxy contracts and diamonds. - -// It greatly simplifies organizing and using state variables in proxy contracts and diamonds. - -// Diamond storage relies on Solidity structs that contain sets of state variables. - -// A struct can be defined with state variables and then used in a particular position in contract -// storage. The position can be determined by a hash of a unique string or other data. The string -// acts like a namespace for the struct. For example a diamond storage string for a struct could -// be 'com.mycompany.projectx.mystruct'. That will look familiar to you if you have used programming -// languages that use namespaces. - -// Namespaces are used in some programming languages to package data and code together as separate -// reusable units. Diamond storage packages sets of state variables as separate, reusable data units -// in contract storage. - -// Let's look at a simple example of diamond storage: - -library LibERC721 { - bytes32 constant ERC721_POSITION = keccak256("erc721.storage"); - - // Instead of using a hash of a string other schemes can be used to create positions in contract storage. - // Here is a scheme that could be used: - // - // bytes32 constant ERC721_POSITION = - // keccak256(abi.encodePacked( - // ERC721.interfaceId, - // ERC721.name - // )); - - struct ERC721Storage { - // tokenId => owner - mapping (uint256 => address) tokenIdToOwner; - // owner => count of tokens owned - mapping (address => uint256) ownerToNFTokenCount; - - string name; - string symbol; - } - - // Return ERC721 storage struct for reading and writing - function getStorage() internal pure returns (ERC721Storage storage storageStruct) { - bytes32 position = ERC721_POSITION; - assembly { - storageStruct.slot := position - } - } - - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - // This is a very simplified implementation. - // It does not include all necessary validation of input. - // It is used to show diamond storage. - function transferFrom(address _from, address _to, uint256 _tokenId) internal { - ERC721Storage storage erc721Storage = LibERC721.getStorage(); - address tokenOwner = erc721Storage.tokenIdToOwner[_tokenId]; - require(tokenOwner == _from); - erc721Storage.tokenIdToOwner[_tokenId] = _to; - erc721Storage.ownerToNFTokenCount[_from]--; - erc721Storage.ownerToNFTokenCount[_to]++; - emit Transfer(_from, _to, _tokenId); - } -} - -// Note that this is not a full or correct ERC721 implementation. -// This is an example of using diamond storage. - -// Note that the ERC721.name and ERC721.symbol storage variables would probably be set -// in an `init` function at deployment time or during an upgrade. - - -// Shows use of LibERC721 and diamond storage -contract ERC721Facet { - - function name() external view returns (string memory name_) { - name_ = LibERC721.getStorage().name; - } - - function symbol() external view returns (string memory symbol_) { - symbol_ = LibERC721.getStorage().symbol; - } - - function transferFrom(address _from, address _to, uint256 _tokenId) external { - LibERC721.transferFrom(_from, _to, _tokenId); - } - -} - -// Here we show how we can share state variables and internal functions between facets by -// using Solidity libraries. Sharing internal functions between facets can also be done by -// inheriting contracts that contain internal functions. -contract ERC721BatchTransferFacet { - - function batchTransferFrom(address _from, address _to, uint256[] calldata _tokenIds) external { - for(uint256 i; i < _tokenIds.length; i++) { - LibERC721.transferFrom(_from, _to, _tokenIds[i]); - } - } -} - -// HOW TO UPGRADE DIAMOND STORAGE -//-------------------------------------------- - -// It is important not to corrupt state variables during an upgrade. It is easy to handle state -// variables correctly in upgrades. - -// Here's some things that can be done: - -// 1. To add new state variables to an AppStorage struct or a Diamond Storage struct, add them -// to the end of the struct. - -// 2. New state variables can be added to the ends of structs that are stored in mappings. - -// 3. The names of state variables can be changed, but that might be confusing if different -// facets are using different names for the same storage locations. - -// Do not do the following: - -// 1. If you are using AppStorage then do not declare and use state variables outside the -// AppStorage struct. Except Diamond Storage can be used. Diamond Storage and AppStorage -// can be used together. - -// 2. Do not add new state variables to the beginning or middle of structs. Doing this -// makes the new state variable overwrite existing state variable data and all state -// variables after the new state variable reference the wrong storage location. - -// 3. Do not put structs directly in structs unless you don’t plan on ever adding more state -// variables to the inner structs. You won't be able to add new state variables to inner -// structs in upgrades. - -// 4. Do not add new state variables to structs that are used in arrays. - -// 5. When using Diamond Storage do not use the same namespace string for different structs. -// This is obvious. Two different structs at the same location will overwrite each other. - -// 6. Do not allow any facet to be able to call `selfdestruct`. This is easy. Simply don’t -// allow the `selfdestruct` command to exist in any facet source code and don’t allow -// that command to be called via a delegatecall. Because `selfdestruct` could delete a -// facet that is used by a diamond, or `selfdestruct` could be used to delete a diamond -// proxy contract. - -// A trick to use inner structs and still enable them to be extended is to put them in mappings. -// A struct stored in a mapping can be extended in upgrades. You could use a value like 0 defined -// with a constant like INNER_STRUCT. Put your structs in mappings and then access them with the -// INNER_STRUCT constant. Example: MyStruct storage mystruct = storage.mystruct[INNER_STRUCT]; - -// Note that any Solidity data type can be used in Diamond Storage or AppStorage structs. It is -// just that structs directly in structs and structs that are used in arrays can’t be extended -// with more state variables in the future. That could be fine in some cases. - -// These rules will make sense if you understand how Solidity assigns storage locations to state -// variables. I recommend reading and understanding this section of the Solidity documentation: -// 'Layout of State Variables in Storage' diff --git a/assets/eip-2537/bench_vectors.md b/assets/eip-2537/bench_vectors.md deleted file mode 100644 index 2d4a0c0..0000000 --- a/assets/eip-2537/bench_vectors.md +++ /dev/null @@ -1,22 +0,0 @@ -# Set of test vectors to perform benchmarking of EIP-2537 - -## Inlined vectors - -Here one can find inputs (encoded with ABI from the main spec spec) that can be considered "worst cases" for "double-and-add" multiplication algorithm, and also some cases for pairing call. Those are purely for convenience of initial benchmarking of the full ABI without manual test generation. - -``` -G1 addition example input = -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7 -G2 addition example input = -0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d938800000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633 -G1 mul double and add worst case = -0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -G2 mul double and add worst case = -00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79beffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -Pairing case for 2 pairs = -0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be -Pairing case for 4 pairs = -0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be -Pairing case for 6 pairs = -0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be -``` diff --git a/assets/eip-2537/field_to_curve.md b/assets/eip-2537/field_to_curve.md deleted file mode 100644 index 94a9ebf..0000000 --- a/assets/eip-2537/field_to_curve.md +++ /dev/null @@ -1,245 +0,0 @@ -# Field element to curve point mapping used by EIP 2537 - -For a BLS12-381 implemented by EIP-2537 a short Weierstrass curve equation y^2 = x^3 + A * x + B has a property that a product AB = 0, so to implement a mapping function two step algorithms is performed: -- Field element is mapped to a some other curve with AB != 0 -- Isogeny is applied to one to one map a point of this other curve to a point on BLS12-381 -- Cofactor is cleared for a point now on BLS12-381 - -Below we describe generic algorithms for mapping and isogeny application, and later on give concrete parameters for the algorithms - -## Helper function to clear a cofactor - -Later on we use a helper function to clear a cofactor of the curve point. It's implemented as - -~~~ - clear_cofactor(P) := h_eff * P -~~~ - -where values of h_eff are given below in parameters sections - -## Simplified SWU for AB != 0 - -The function map\_to\_curve\_simple\_swu(u) implements a simplification -of the Shallue-van de Woestijne-Ulas mapping described by Brier et -al., which they call the "simplified SWU" map. Wahby and Boneh generalize and optimize this mapping. - -Preconditions: A Weierstrass curve y^2 = g(x) x^3 + A * x + B where A != 0 and B != 0. - -Constants: - -- A and B, the parameters of the Weierstrass curve. - -- Z, an element of F meeting the below criteria. - The criteria are: - 1. Z is non-square in F, - 2. Z != -1 in F, - 3. the polynomial g(x) - Z is irreducible over F, and - 4. g(B / (Z * A)) is square in F. - -Sign of y: Inputs u and -u give the same x-coordinate. -Thus, we set sgn0(y) == sgn0(u). - -Exceptions: The exceptional cases are values of u such that -Z^2 * u^4 + Z * u^2 == 0. This includes u == 0, and may include -other values depending on Z. Implementations must detect -this case and set x1 = B / (Z * A), which guarantees that g(x1) -is square by the condition on Z given above. - -Operations: - -~~~ -1. tv1 = inv0(Z^2 * u^4 + Z * u^2) -2. x1 = (-B / A) * (1 + tv1) -3. If tv1 == 0, set x1 = B / (Z * A) -4. gx1 = x1^3 + A * x1 + B -5. x2 = Z * u^2 * x1 -6. gx2 = x2^3 + A * x2 + B -7. If is_square(gx1), set x = x1 and y = sqrt(gx1) -8. Else set x = x2 and y = sqrt(gx2) -9. If sgn0(u) != sgn0(y), set y = -y -10. return (x, y) -~~~ - -## Simplified SWU for AB == 0 - -Wahby and Boneh show how to adapt the simplified SWU mapping to -Weierstrass curves having A == 0 or B == 0, which the mapping of -simple SWU does not support. - -This method requires finding another elliptic curve E' given by the equation - -~~~ - y'^2 = g'(x') = x'^3 + A' * x' + B' -~~~ - -that is isogenous to E and has A' != 0 and B' != 0. -This isogeny defines a map iso\_map(x', y') given by a pair of rational functions. -iso\_map takes as input a point on E' and produces as output a point on E. - -Once E' and iso\_map are identified, this mapping works as follows: on input -u, first apply the simplified SWU mapping to get a point on E', then apply -the isogeny map to that point to get a point on E. - -Note that iso\_map is a group homomorphism, meaning that point addition -commutes with iso\_map. -Thus, when using this mapping in the hash\_to\_curve construction of {{roadmap}}, -one can effect a small optimization by first mapping u0 and u1 to E', adding -the resulting points on E', and then applying iso\_map to the sum. -This gives the same result while requiring only one evaluation of iso\_map. - -Preconditions: An elliptic curve E' with A' != 0 and B' != 0 that is -isogenous to the target curve E with isogeny map iso\_map from -E' to E. - -So the full mapping algorithm looks as: - -- map\_to\_curve\_simple\_swu is the simple SWU mapping to E' -- iso\_map is the isogeny map from E' to E - -Sign of y: for this map, the sign is determined by map\_to\_curve\_simple\_swu. -No further sign adjustments are necessary. - -Exceptions: map\_to\_curve\_simple\_swu handles its exceptional cases. -Exceptional cases of iso\_map are inputs that cause the denominator of -either rational function to evaluate to zero; such cases MUST return the -identity point on E. - -## Full algorithm restated - -~~~ -1. (x', y') = map_to_curve_simple_swu(u) # (x', y') is on E' -2. (x, y) = iso_map(x', y') # (x, y) is on E -3. (x, y) = clear_cofactor((x, y)) # clears cofactor for point (x, y) on E -4. return (x, y) -~~~ - -## Parameters for EIP-2537 - -### Fp-to-G1 mapping - - -- Z: 11 -- E': y'^2 = x'^3 + A' * x' + B', where - - A' = 0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d - - B' = 0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0 -- h\_eff: 0xd201000000010001 - -The 11-isogeny map from (x', y') on E' to (x, y) on E is given by the following rational functions: - -- x = x\_num / x\_den, where - - x\_num = k\_(1,11) * x'^11 + k\_(1,10) * x'^10 + k\_(1,9) * x'^9 + ... + k\_(1,0) - - x\_den = x'^10 + k\_(2,9) * x'^9 + k\_(2,8) * x'^8 + ... + k\_(2,0) - -- y = y' * y\_num / y\_den, where - - y\_num = k\_(3,15) * x'^15 + k\_(3,14) * x'^14 + k\_(3,13) * x'^13 + ... + k\_(3,0) - - y\_den = x'^15 + k\_(4,14) * x'^14 + k\_(4,13) * x'^13 + ... + k\_(4,0) - -The constants used to compute x\_num are as follows: - -- k\_(1,0) = 0x11a05f2b1e833340b809101dd99815856b303e88a2d7005ff2627b56cdb4e2c85610c2d5f2e62d6eaeac1662734649b7 -- k\_(1,1) = 0x17294ed3e943ab2f0588bab22147a81c7c17e75b2f6a8417f565e33c70d1e86b4838f2a6f318c356e834eef1b3cb83bb -- k\_(1,2) = 0xd54005db97678ec1d1048c5d10a9a1bce032473295983e56878e501ec68e25c958c3e3d2a09729fe0179f9dac9edcb0 -- k\_(1,3) = 0x1778e7166fcc6db74e0609d307e55412d7f5e4656a8dbf25f1b33289f1b330835336e25ce3107193c5b388641d9b6861 -- k\_(1,4) = 0xe99726a3199f4436642b4b3e4118e5499db995a1257fb3f086eeb65982fac18985a286f301e77c451154ce9ac8895d9 -- k\_(1,5) = 0x1630c3250d7313ff01d1201bf7a74ab5db3cb17dd952799b9ed3ab9097e68f90a0870d2dcae73d19cd13c1c66f652983 -- k\_(1,6) = 0xd6ed6553fe44d296a3726c38ae652bfb11586264f0f8ce19008e218f9c86b2a8da25128c1052ecaddd7f225a139ed84 -- k\_(1,7) = 0x17b81e7701abdbe2e8743884d1117e53356de5ab275b4db1a682c62ef0f2753339b7c8f8c8f475af9ccb5618e3f0c88e -- k\_(1,8) = 0x80d3cf1f9a78fc47b90b33563be990dc43b756ce79f5574a2c596c928c5d1de4fa295f296b74e956d71986a8497e317 -- k\_(1,9) = 0x169b1f8e1bcfa7c42e0c37515d138f22dd2ecb803a0c5c99676314baf4bb1b7fa3190b2edc0327797f241067be390c9e -- k\_(1,10) = 0x10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b -- k\_(1,11) = 0x6e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229 - -The constants used to compute x\_den are as follows: - -- k\_(2,0) = 0x8ca8d548cff19ae18b2e62f4bd3fa6f01d5ef4ba35b48ba9c9588617fc8ac62b558d681be343df8993cf9fa40d21b1c -- k\_(2,1) = 0x12561a5deb559c4348b4711298e536367041e8ca0cf0800c0126c2588c48bf5713daa8846cb026e9e5c8276ec82b3bff -- k\_(2,2) = 0xb2962fe57a3225e8137e629bff2991f6f89416f5a718cd1fca64e00b11aceacd6a3d0967c94fedcfcc239ba5cb83e19 -- k\_(2,3) = 0x3425581a58ae2fec83aafef7c40eb545b08243f16b1655154cca8abc28d6fd04976d5243eecf5c4130de8938dc62cd8 -- k\_(2,4) = 0x13a8e162022914a80a6f1d5f43e7a07dffdfc759a12062bb8d6b44e833b306da9bd29ba81f35781d539d395b3532a21e -- k\_(2,5) = 0xe7355f8e4e667b955390f7f0506c6e9395735e9ce9cad4d0a43bcef24b8982f7400d24bc4228f11c02df9a29f6304a5 -- k\_(2,6) = 0x772caacf16936190f3e0c63e0596721570f5799af53a1894e2e073062aede9cea73b3538f0de06cec2574496ee84a3a -- k\_(2,7) = 0x14a7ac2a9d64a8b230b3f5b074cf01996e7f63c21bca68a81996e1cdf9822c580fa5b9489d11e2d311f7d99bbdcc5a5e -- k\_(2,8) = 0xa10ecf6ada54f825e920b3dafc7a3cce07f8d1d7161366b74100da67f39883503826692abba43704776ec3a79a1d641 -- k\_(2,9) = 0x95fc13ab9e92ad4476d6e3eb3a56680f682b4ee96f7d03776df533978f31c1593174e4b4b7865002d6384d168ecdd0a - -The constants used to compute y\_num are as follows: - -- k\_(3,0) = 0x90d97c81ba24ee0259d1f094980dcfa11ad138e48a869522b52af6c956543d3cd0c7aee9b3ba3c2be9845719707bb33 -- k\_(3,1) = 0x134996a104ee5811d51036d776fb46831223e96c254f383d0f906343eb67ad34d6c56711962fa8bfe097e75a2e41c696 -- k\_(3,2) = 0xcc786baa966e66f4a384c86a3b49942552e2d658a31ce2c344be4b91400da7d26d521628b00523b8dfe240c72de1f6 -- k\_(3,3) = 0x1f86376e8981c217898751ad8746757d42aa7b90eeb791c09e4a3ec03251cf9de405aba9ec61deca6355c77b0e5f4cb -- k\_(3,4) = 0x8cc03fdefe0ff135caf4fe2a21529c4195536fbe3ce50b879833fd221351adc2ee7f8dc099040a841b6daecf2e8fedb -- k\_(3,5) = 0x16603fca40634b6a2211e11db8f0a6a074a7d0d4afadb7bd76505c3d3ad5544e203f6326c95a807299b23ab13633a5f0 -- k\_(3,6) = 0x4ab0b9bcfac1bbcb2c977d027796b3ce75bb8ca2be184cb5231413c4d634f3747a87ac2460f415ec961f8855fe9d6f2 -- k\_(3,7) = 0x987c8d5333ab86fde9926bd2ca6c674170a05bfe3bdd81ffd038da6c26c842642f64550fedfe935a15e4ca31870fb29 -- k\_(3,8) = 0x9fc4018bd96684be88c9e221e4da1bb8f3abd16679dc26c1e8b6e6a1f20cabe69d65201c78607a360370e577bdba587 -- k\_(3,9) = 0xe1bba7a1186bdb5223abde7ada14a23c42a0ca7915af6fe06985e7ed1e4d43b9b3f7055dd4eba6f2bafaaebca731c30 -- k\_(3,10) = 0x19713e47937cd1be0dfd0b8f1d43fb93cd2fcbcb6caf493fd1183e416389e61031bf3a5cce3fbafce813711ad011c132 -- k\_(3,11) = 0x18b46a908f36f6deb918c143fed2edcc523559b8aaf0c2462e6bfe7f911f643249d9cdf41b44d606ce07c8a4d0074d8e -- k\_(3,12) = 0xb182cac101b9399d155096004f53f447aa7b12a3426b08ec02710e807b4633f06c851c1919211f20d4c04f00b971ef8 -- k\_(3,13) = 0x245a394ad1eca9b72fc00ae7be315dc757b3b080d4c158013e6632d3c40659cc6cf90ad1c232a6442d9d3f5db980133 -- k\_(3,14) = 0x5c129645e44cf1102a159f748c4a3fc5e673d81d7e86568d9ab0f5d396a7ce46ba1049b6579afb7866b1e715475224b -- k\_(3,15) = 0x15e6be4e990f03ce4ea50b3b42df2eb5cb181d8f84965a3957add4fa95af01b2b665027efec01c7704b456be69c8b604 - -The constants used to compute y\_den are as follows: - -- k\_(4,0) = 0x16112c4c3a9c98b252181140fad0eae9601a6de578980be6eec3232b5be72e7a07f3688ef60c206d01479253b03663c1 -- k\_(4,1) = 0x1962d75c2381201e1a0cbd6c43c348b885c84ff731c4d59ca4a10356f453e01f78a4260763529e3532f6102c2e49a03d -- k\_(4,2) = 0x58df3306640da276faaae7d6e8eb15778c4855551ae7f310c35a5dd279cd2eca6757cd636f96f891e2538b53dbf67f2 -- k\_(4,3) = 0x16b7d288798e5395f20d23bf89edb4d1d115c5dbddbcd30e123da489e726af41727364f2c28297ada8d26d98445f5416 -- k\_(4,4) = 0xbe0e079545f43e4b00cc912f8228ddcc6d19c9f0f69bbb0542eda0fc9dec916a20b15dc0fd2ededda39142311a5001d -- k\_(4,5) = 0x8d9e5297186db2d9fb266eaac783182b70152c65550d881c5ecd87b6f0f5a6449f38db9dfa9cce202c6477faaf9b7ac -- k\_(4,6) = 0x166007c08a99db2fc3ba8734ace9824b5eecfdfa8d0cf8ef5dd365bc400a0051d5fa9c01a58b1fb93d1a1399126a775c -- k\_(4,7) = 0x16a3ef08be3ea7ea03bcddfabba6ff6ee5a4375efa1f4fd7feb34fd206357132b920f5b00801dee460ee415a15812ed9 -- k\_(4,8) = 0x1866c8ed336c61231a1be54fd1d74cc4f9fb0ce4c6af5920abc5750c4bf39b4852cfe2f7bb9248836b233d9d55535d4a -- k\_(4,9) = 0x167a55cda70a6e1cea820597d94a84903216f763e13d87bb5308592e7ea7d4fbc7385ea3d529b35e346ef48bb8913f55 -- k\_(4,10) = 0x4d2f259eea405bd48f010a01ad2911d9c6dd039bb61a6290e591b36e636a5c871a5c29f4f83060400f8b49cba8f6aa8 -- k\_(4,11) = 0xaccbb67481d033ff5852c1e48c50c477f94ff8aefce42d28c0f9a88cea7913516f968986f7ebbea9684b529e2561092 -- k\_(4,12) = 0xad6b9514c767fe3c3613144b45f1496543346d98adf02267d5ceef9a00d9b8693000763e3b90ac11e99b138573345cc -- k\_(4,13) = 0x2660400eb2e4f3b628bdd0d53cd76f2bf565b94e72927c1cb748df27942480e420517bd8714cc80d1fadc1326ed06f7 -- k\_(4,14) = 0xe0fa1d816ddc03e6b24255e0d7819c171c40f65e273b853324efcd6356caa205ca2f570f13497804415473a1d634b8f - -### Fp2-to-G2 mapping - -Symbol `I` means a non-residue used to make an extension field Fp2 - -- Z: -(2 + I) -- E': y'^2 = x'^3 + A' * x' + B', where - - A' = 240 * I - - B' = 1012 * (1 + I) -- h\_eff: 0xbc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551 - -The 3-isogeny map from (x', y') on E' to (x, y) on E is given by the following rational functions: - -- x = x\_num / x\_den, where - - x\_num = k\_(1,3) * x'^3 + k\_(1,2) * x'^2 + k\_(1,1) * x' + k\_(1,0) - - x\_den = x'^2 + k\_(2,1) * x' + k\_(2,0) - -- y = y' * y\_num / y\_den, where - - y\_num = k\_(3,3) * x'^3 + k\_(3,2) * x'^2 + k\_(3,1) * x' + k\_(3,0) - - y\_den = x'^3 + k\_(4,2) * x'^2 + k\_(4,1) * x' + k\_(4,0) - -The constants used to compute x\_num are as follows: - -- k\_(1,0) = 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6 + 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6 * I -- k\_(1,1) = 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71a * I -- k\_(1,2) = 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71e + 0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38d * I -- k\_(1,3) = 0x171d6541fa38ccfaed6dea691f5fb614cb14b4e7f4e810aa22d6108f142b85757098e38d0f671c7188e2aaaaaaaa5ed1 - -The constants used to compute x\_den are as follows: - -- k\_(2,0) = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa63 * I -- k\_(2,1) = 0xc + 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa9f * I - -The constants used to compute y\_num are as follows: - -- k\_(3,0) = 0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706 + 0x1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706 * I -- k\_(3,1) = 0x5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97be * I -- k\_(3,2) = 0x11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71c + 0x8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38f * I -- k\_(3,3) = 0x124c9ad43b6cf79bfbf7043de3811ad0761b0f37a1e26286b0e977c69aa274524e79097a56dc4bd9e1b371c71c718b10 - -The constants used to compute y\_den are as follows: - -- k\_(4,0) = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb + 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb * I -- k\_(4,1) = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa9d3 * I -- k\_(4,2) = 0x12 + 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa99 * I \ No newline at end of file diff --git a/assets/eip-2565/Complexity_Regression.png b/assets/eip-2565/Complexity_Regression.png deleted file mode 100644 index 059671b..0000000 Binary files a/assets/eip-2565/Complexity_Regression.png and /dev/null differ diff --git a/assets/eip-2565/GQuad_Change.png b/assets/eip-2565/GQuad_Change.png deleted file mode 100644 index 53bb641..0000000 Binary files a/assets/eip-2565/GQuad_Change.png and /dev/null differ diff --git a/assets/eip-2615/concept.png b/assets/eip-2615/concept.png deleted file mode 100644 index 3198468..0000000 Binary files a/assets/eip-2615/concept.png and /dev/null differ diff --git a/assets/eip-2615/mortgage-sequential.jpg b/assets/eip-2615/mortgage-sequential.jpg deleted file mode 100644 index 738c8f4..0000000 Binary files a/assets/eip-2615/mortgage-sequential.jpg and /dev/null differ diff --git a/assets/eip-2615/rental-sequential.jpg b/assets/eip-2615/rental-sequential.jpg deleted file mode 100644 index 462f655..0000000 Binary files a/assets/eip-2615/rental-sequential.jpg and /dev/null differ diff --git a/assets/eip-2678/package.spec.json b/assets/eip-2678/package.spec.json deleted file mode 100644 index f78790e..0000000 --- a/assets/eip-2678/package.spec.json +++ /dev/null @@ -1,483 +0,0 @@ -{ - "title": "Package Manifest", - "description": "EthPM Manifest Specification", - "type": "object", - "required": [ - "manifest" - ], - "version": "3", - "not": { - "required": ["manifest_version"] - }, - "properties": { - "manifest": { - "type": "string", - "title": "Manifest", - "description": "EthPM Manifest Version", - "default": "ethpm/3", - "enum": ["ethpm/3"] - }, - "name": { - "$ref": "#/definitions/PackageName" - }, - "version": { - "title": "Package Version", - "description": "The version of the package that this release is for", - "type": "string" - }, - "meta": { - "$ref": "#/definitions/PackageMeta" - }, - "sources": { - "title": "Sources", - "description": "The source files included in this release", - "type": "object", - "patternProperties": { - ".*": { - "$ref": "#/definitions/Source" - } - } - }, - "compilers": { - "title": "Compilers", - "description": "The compiler versions used in this release", - "type": "array", - "items": { - "$ref": "#/definitions/CompilerInformation" - } - }, - "contractTypes": { - "title": "Contract Types", - "description": "The contract types included in this release", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/ContractTypeName" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/ContractType" - } - } - }, - "deployments": { - "title": "Deployments", - "description": "The deployed contract instances in this release", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/BlockchainURI" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/Deployment" - } - } - }, - "buildDependencies": { - "title": "Build Dependencies", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/PackageName" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/ContentURI" - } - } - } - }, - "definitions": { - "Source": { - "title": "Source", - "description": "Information about a source file included in this package", - "type": "object", - "anyOf": [ - {"required": ["content"]}, - {"required": ["urls"]} - ], - "properties": { - "checksum": { - "$ref": "#/definitions/ChecksumObject" - }, - "urls": { - "title": "URLs", - "description": "Array of urls that resolve to the source file", - "type": "array", - "items": { - "$ref": "#/definitions/ContentURI" - } - }, - "content": { - "title": "Inlined source code", - "type": "string" - }, - "installPath": { - "title": "Target file path to install source file", - "type": "string", - "pattern": "^\\.\\/.*$" - }, - "type": { - "title": "Type", - "description": "File type of this source", - "type": "string" - }, - "license": { - "title": "License", - "description": "The license associated with the source file", - "type": "string" - } - } - }, - "PackageMeta": { - "title": "Package Meta", - "description": "Metadata about the package", - "type": "object", - "properties": { - "authors": { - "title": "Authors", - "description": "Authors of this package", - "type": "array", - "items": { - "type": "string" - } - }, - "license": { - "title": "License", - "description": "The license that this package and its source are released under", - "type": "string" - }, - "description": { - "title": "Description", - "description": "Description of this package", - "type": "string" - }, - "keywords": { - "title": "Keywords", - "description": "Keywords that apply to this package", - "type": "array", - "items": { - "type": "string" - } - }, - "links": { - "title": "Links", - "descriptions": "URIs for resources related to this package", - "type": "object", - "additionalProperties": { - "type": "string", - "format": "uri" - } - } - } - }, - "PackageName": { - "title": "Package Name", - "description": "The name of the package that this release is for", - "type": "string", - "pattern": "^[a-z][-a-z0-9]{0,255}$" - }, - "ContractType": { - "title": "Contract Type", - "description": "Data for a contract type included in this package", - "type": "object", - "properties":{ - "contractName": { - "$ref": "#/definitions/ContractTypeName" - }, - "sourceId": { - "title": "Source ID", - "description": "The source ID that corresponds to this contract type", - "type": "string" - }, - "deploymentBytecode": { - "$ref": "#/definitions/BytecodeObject" - }, - "runtimeBytecode": { - "$ref": "#/definitions/BytecodeObject" - }, - "abi": { - "title": "ABI", - "description": "The ABI for this contract type", - "type": "array" - }, - "devdoc": { - "title": "Devdoc", - "description": "The dev-doc for this contract", - "type": "object" - }, - "userdoc": { - "title": "Userdoc", - "description": "The user-doc for this contract", - "type": "object" - } - } - }, - "ContractInstance": { - "title": "Contract Instance", - "description": "Data for a deployed instance of a contract", - "type": "object", - "required": [ - "contractType", - "address" - ], - "properties": { - "contractType": { - "anyOf": [ - {"$ref": "#/definitions/ContractTypeName"}, - {"$ref": "#/definitions/NestedContractTypeName"} - ] - }, - "address": { - "$ref": "#/definitions/Address" - }, - "transaction": { - "$ref": "#/definitions/TransactionHash" - }, - "block": { - "$ref": "#/definitions/BlockHash" - }, - "runtimeBytecode": { - "$ref": "#/definitions/BytecodeObject" - }, - "linkDependencies": { - "title": "Link Dependencies", - "description": "The values for the link references found within this contract instances runtime bytecode", - "type": "array", - "items": { - "$ref": "#/definitions/LinkValue" - } - } - } - }, - "ContractTypeName": { - "title": "Contract Type Name", - "description": "The name of the contract type", - "type": "string", - "pattern": "^(?:[a-z][-a-z0-9]{0,255}\\:)?[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256}])?$" - }, - "ByteString": { - "title": "Byte String", - "description": "0x-prefixed hexadecimal string representing bytes", - "type": "string", - "pattern": "^0x([0-9a-fA-F]{2})*$" - }, - "BytecodeObject": { - "title": "Bytecode Object", - "type": "object", - "anyOf": [ - {"required": ["bytecode"]}, - {"required": ["linkDependencies"]} - ], - "properties": { - "bytecode": { - "$ref": "#/definitions/ByteString" - }, - "linkReferences": { - "type": "array", - "items": { - "$ref": "#/definitions/LinkReference" - } - }, - "linkDependencies": { - "type": "array", - "items": { - "$ref": "#/definitions/LinkValue" - } - } - } - }, - "ChecksumObject": { - "title": "Checksum Object", - "description": "Checksum information about the contents of a source file", - "type": "object", - "required": [ - "hash", - "algorithm" - ], - "properties": { - "hash": { - "type": "string" - }, - "algorithm": { - "type": "string" - } - } - }, - "LinkReference": { - "title": "Link Reference", - "description": "A defined location in some bytecode which requires linking", - "type": "object", - "required": [ - "offsets", - "length", - "name" - ], - "properties": { - "offsets": { - "type": "array", - "items": { - "type": "integer", - "minimum": 0 - } - }, - "length": { - "type": "integer", - "minimum": 1 - }, - "name": { - "anyOf": [ - {"$ref": "#/definitions/ContractTypeName"}, - {"$ref": "#/definitions/NestedContractTypeName"} - ] - } - } - }, - "LinkValue": { - "title": "Link Value", - "description": "A value for an individual link reference in a contract's bytecode", - "type": "object", - "required": [ - "offsets", - "type", - "value" - ], - "properties": { - "offsets": { - "type": "array", - "items": { - "type": "integer", - "minimum": 0 - } - }, - "type": { - "description": "The type of link value", - "type": "string" - }, - "value": { - "description": "The value for the link reference" - } - }, - "oneOf": [{ - "properties": { - "type": { - "enum": ["literal"] - }, - "value": { - "$ref": "#/definitions/ByteString" - } - } - }, { - "properties": { - "type": { - "enum": ["reference"] - }, - "value": { - "anyOf": [ - {"$ref": "#/definitions/ContractInstanceName"}, - {"$ref": "#/definitions/NestedContractInstanceName"} - ] - } - } - }] - }, - "ContractInstanceName": { - "title": "Contract Instance Name", - "description": "The name of the deployed contract instance", - "type": "string", - "pattern": "^[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256})?$" - }, - "Deployment": { - "title": "Deployment", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/ContractInstanceName" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/ContractInstance" - } - } - }, - "NestedContractTypeName": { - "title": "Nested Contract Type Name", - "description": "Name of a nested contract type from somewhere down the dependency tree", - "type": "string", - "pattern": "^(?:[a-z][-a-z0-9]{0,255}\\:)+[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256})?$" - }, - "NestedContractInstanceName": { - "title": "Nested Contract Instance Name", - "description": "Name of a nested contract instance from somewhere down the dependency tree", - "type": "string", - "pattern": "^(?:[a-z][-a-z0-9]{0,255}\\:)+[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256})?$" - }, - "CompilerInformation": { - "title": "Compiler Information", - "description": "Information about the software that was used to compile a contract type or deployment", - "type": "object", - "required": [ - "name", - "version" - ], - "properties": { - "name": { - "description": "The name of the compiler", - "type": "string" - }, - "version": { - "description": "The version string for the compiler", - "type": "string" - }, - "settings": { - "description": "The settings used for compilation", - "type": "object" - }, - "contractTypes": { - "description": "The contract types that targeted this compiler.", - "type": "array", - "items": { - "$ref": "#/definitions/ContractTypeName" - } - } - } - }, - "Address": { - "title": "Address", - "description": "A 0x-prefixed Ethereum address", - "allOf": [ - { "$ref": "#/definitions/ByteString" }, - { "minLength": 42, "maxLength": 42 } - ] - }, - "TransactionHash": { - "title": "Transaction Hash", - "description": "A 0x-prefixed Ethereum transaction hash", - "allOf": [ - { "$ref": "#/definitions/ByteString" }, - { "minLength": 66, "maxLength": 66 } - ] - }, - "BlockHash": { - "title": "Block Hash", - "description": "An Ethereum block hash", - "allOf": [ - { "$ref": "#/definitions/ByteString" }, - { "minLength": 66, "maxLength": 66 } - ] - }, - "ContentURI": { - "title": "Content URI", - "description": "An content addressable URI", - "type": "string", - "format": "uri" - }, - "BlockchainURI": { - "title": "Blockchain URI", - "description": "An BIP122 URI", - "type": "string", - "pattern": "^blockchain\\://[0-9a-fA-F]{64}\\/block\\/[0-9a-fA-F]{64}$" - } - }, - "dependencies": { - "name": ["version"], - "version": ["name"] - } -} diff --git a/assets/eip-2680/sha256-384-512.pdf b/assets/eip-2680/sha256-384-512.pdf deleted file mode 100644 index 0524e81..0000000 Binary files a/assets/eip-2680/sha256-384-512.pdf and /dev/null differ diff --git a/assets/eip-2771/example-flow.png b/assets/eip-2771/example-flow.png deleted file mode 100644 index b122417..0000000 Binary files a/assets/eip-2771/example-flow.png and /dev/null differ diff --git a/assets/eip-2848/presentation.pdf b/assets/eip-2848/presentation.pdf deleted file mode 100644 index 99446f4..0000000 Binary files a/assets/eip-2848/presentation.pdf and /dev/null differ diff --git a/assets/eip-2917/erc-reward-formula.png b/assets/eip-2917/erc-reward-formula.png deleted file mode 100644 index a50f387..0000000 Binary files a/assets/eip-2917/erc-reward-formula.png and /dev/null differ diff --git a/assets/eip-2980/Finma-ICO-Guidelines.pdf b/assets/eip-2980/Finma-ICO-Guidelines.pdf deleted file mode 100644 index 2c8ba8d..0000000 Binary files a/assets/eip-2980/Finma-ICO-Guidelines.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-AMLA.pdf b/assets/eip-2980/Swiss-Confederation-AMLA.pdf deleted file mode 100644 index 831c89c..0000000 Binary files a/assets/eip-2980/Swiss-Confederation-AMLA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-BA.pdf b/assets/eip-2980/Swiss-Confederation-BA.pdf deleted file mode 100644 index 231a551..0000000 Binary files a/assets/eip-2980/Swiss-Confederation-BA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-CISA.pdf b/assets/eip-2980/Swiss-Confederation-CISA.pdf deleted file mode 100644 index 3f87003..0000000 Binary files a/assets/eip-2980/Swiss-Confederation-CISA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-FINIA.pdf b/assets/eip-2980/Swiss-Confederation-FINIA.pdf deleted file mode 100644 index e070984..0000000 Binary files a/assets/eip-2980/Swiss-Confederation-FINIA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-FINSA.pdf b/assets/eip-2980/Swiss-Confederation-FINSA.pdf deleted file mode 100644 index 154aec3..0000000 Binary files a/assets/eip-2980/Swiss-Confederation-FINSA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-FMIA.pdf b/assets/eip-2980/Swiss-Confederation-FMIA.pdf deleted file mode 100644 index 959c239..0000000 Binary files a/assets/eip-2980/Swiss-Confederation-FMIA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-SESTA.pdf b/assets/eip-2980/Swiss-Confederation-SESTA.pdf deleted file mode 100644 index b83062d..0000000 Binary files a/assets/eip-2980/Swiss-Confederation-SESTA.pdf and /dev/null differ diff --git a/assets/eip-2982/2982-issuance.png b/assets/eip-2982/2982-issuance.png deleted file mode 100644 index 9534a86..0000000 Binary files a/assets/eip-2982/2982-issuance.png and /dev/null differ diff --git a/assets/eip-2982/arxiv-1710.09437-Casper-the-Friendly-Finality-Gadget.pdf b/assets/eip-2982/arxiv-1710.09437-Casper-the-Friendly-Finality-Gadget.pdf deleted file mode 100644 index 9c67edb..0000000 Binary files a/assets/eip-2982/arxiv-1710.09437-Casper-the-Friendly-Finality-Gadget.pdf and /dev/null differ diff --git a/assets/eip-2982/arxiv-1809.09044-Fraud-and-Data-Availability-Proofs--Maximising-Light-Client-Security-and-Scaling-Blockchains-with-Dishonest-Majorities.pdf b/assets/eip-2982/arxiv-1809.09044-Fraud-and-Data-Availability-Proofs--Maximising-Light-Client-Security-and-Scaling-Blockchains-with-Dishonest-Majorities.pdf deleted file mode 100644 index 7f57657..0000000 Binary files a/assets/eip-2982/arxiv-1809.09044-Fraud-and-Data-Availability-Proofs--Maximising-Light-Client-Security-and-Scaling-Blockchains-with-Dishonest-Majorities.pdf and /dev/null differ diff --git a/assets/eip-2982/arxiv-2003.03052-Combining-GHOST-and-Casper.pdf b/assets/eip-2982/arxiv-2003.03052-Combining-GHOST-and-Casper.pdf deleted file mode 100644 index 24d6e69..0000000 Binary files a/assets/eip-2982/arxiv-2003.03052-Combining-GHOST-and-Casper.pdf and /dev/null differ diff --git a/assets/eip-2982/ef-Discouragement-Attacks.pdf b/assets/eip-2982/ef-Discouragement-Attacks.pdf deleted file mode 100644 index 8dad60a..0000000 Binary files a/assets/eip-2982/ef-Discouragement-Attacks.pdf and /dev/null differ diff --git a/assets/eip-2982/iacr-2015-702-Demystifying-Incentives-in-the-Consensus-Computer.pdf b/assets/eip-2982/iacr-2015-702-Demystifying-Incentives-in-the-Consensus-Computer.pdf deleted file mode 100644 index 31cbeef..0000000 Binary files a/assets/eip-2982/iacr-2015-702-Demystifying-Incentives-in-the-Consensus-Computer.pdf and /dev/null differ diff --git a/assets/eip-3005/meta-txs-directly-to-token-smart-contract.png b/assets/eip-3005/meta-txs-directly-to-token-smart-contract.png deleted file mode 100644 index 2c792f1..0000000 Binary files a/assets/eip-3005/meta-txs-directly-to-token-smart-contract.png and /dev/null differ diff --git a/assets/eip-3068/2012-685_Square_Root_Even_Ext.pdf b/assets/eip-3068/2012-685_Square_Root_Even_Ext.pdf deleted file mode 100644 index 077e093..0000000 Binary files a/assets/eip-3068/2012-685_Square_Root_Even_Ext.pdf and /dev/null differ diff --git a/assets/eip-3068/2015-1027_exTNFS.pdf b/assets/eip-3068/2015-1027_exTNFS.pdf deleted file mode 100644 index a121ddb..0000000 Binary files a/assets/eip-3068/2015-1027_exTNFS.pdf and /dev/null differ diff --git a/assets/eip-3068/2016-1102_Assessing_NFS_Advances.pdf b/assets/eip-3068/2016-1102_Assessing_NFS_Advances.pdf deleted file mode 100644 index 2b42969..0000000 Binary files a/assets/eip-3068/2016-1102_Assessing_NFS_Advances.pdf and /dev/null differ diff --git a/assets/eip-3068/2017-334.pdf b/assets/eip-3068/2017-334.pdf deleted file mode 100644 index 297ded4..0000000 Binary files a/assets/eip-3068/2017-334.pdf and /dev/null differ diff --git a/assets/eip-3068/2019-403_BLS12_H2C.pdf b/assets/eip-3068/2019-403_BLS12_H2C.pdf deleted file mode 100644 index 99a6ace..0000000 Binary files a/assets/eip-3068/2019-403_BLS12_H2C.pdf and /dev/null differ diff --git a/assets/eip-3068/latincrypt12.pdf b/assets/eip-3068/latincrypt12.pdf deleted file mode 100644 index 11ebc47..0000000 Binary files a/assets/eip-3068/latincrypt12.pdf and /dev/null differ diff --git a/assets/eip-3068/madnet.pdf b/assets/eip-3068/madnet.pdf deleted file mode 100644 index 5185154..0000000 Binary files a/assets/eip-3068/madnet.pdf and /dev/null differ diff --git a/assets/eip-3068/weilsigs.pdf b/assets/eip-3068/weilsigs.pdf deleted file mode 100644 index 125c14f..0000000 Binary files a/assets/eip-3068/weilsigs.pdf and /dev/null differ diff --git a/assets/eip-3074/auth-msg-multi-call.png b/assets/eip-3074/auth-msg-multi-call.png deleted file mode 100644 index 55da835..0000000 Binary files a/assets/eip-3074/auth-msg-multi-call.png and /dev/null differ diff --git a/assets/eip-3074/auth-msg.png b/assets/eip-3074/auth-msg.png deleted file mode 100644 index 2fc38d6..0000000 Binary files a/assets/eip-3074/auth-msg.png and /dev/null differ diff --git a/assets/eip-3102/sibling.svg b/assets/eip-3102/sibling.svg deleted file mode 100644 index f670dc1..0000000 --- a/assets/eip-3102/sibling.svg +++ /dev/null @@ -1,3 +0,0 @@ - - -
root
root
...
...
4
4
2
2
2
2
root
root
...
...
5
5
8
8
8
8
root
root
...
...
8
8
Delete 10000100
Delete 100...
root
root
...
...
7
7
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/assets/eip-3267/contracts/BaseBidOnAddresses.sol b/assets/eip-3267/contracts/BaseBidOnAddresses.sol deleted file mode 100644 index ca69346..0000000 --- a/assets/eip-3267/contracts/BaseBidOnAddresses.sol +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import { ABDKMath64x64 } from "abdk-libraries-solidity/ABDKMath64x64.sol"; -import { BaseLock } from "./BaseLock.sol"; - -/// @title Bidding on Ethereum addresses -/// @author Victor Porton -/// @notice Not audited, not enough tested. -/// This allows anyone claim conditional tokens in order for him to transfer money from the future. -/// See `docs/future-money.rst`. -abstract contract BaseBidOnAddresses is BaseLock { - using ABDKMath64x64 for int128; - using SafeMath for uint256; - - /// A condition score was stored in the chain by an oracle. - /// @param oracleId The oracle ID. - /// @param condition The conditional (customer addresses). - /// @param numerator The relative score provided by the oracle. - event ReportedNumerator( - uint64 indexed oracleId, - uint256 indexed condition, - uint256 numerator - ); - - /// Some condition scores were stored in the chain by an oracle. - /// @param oracleId The oracle ID. - /// @param conditions The conditionals (customer addresses). - /// @param numerators The relative scores provided by the oracle. - event ReportedNumeratorsBatch( - uint64 indexed oracleId, - uint64[] indexed conditions, - uint256[] numerators - ); - - // Whether an oracle finished its work. - mapping(uint64 => bool) private oracleFinishedMap; - // Mapping (oracleId => (condition => numerator)) for payout numerators. - mapping(uint64 => mapping(uint256 => uint256)) private payoutNumeratorsMap; - // Mapping (oracleId => denominator) for payout denominators. - mapping(uint256 => uint) private payoutDenominatorMap; - - /// Constructor. - /// @param _uri Our ERC-1155 tokens description URI. - constructor(string memory _uri) BaseLock(_uri) { } - - /// Retrieve the last stored payout numerator (relative score of a condition). - /// @param _oracleId The oracle ID. - /// @param _condition The condition (the original receiver of a conditional token). - /// The result can't change if the oracle has finished. - function payoutNumerator(uint64 _oracleId, uint256 _condition) public view returns (uint256) { - return payoutNumeratorsMap[_oracleId][_condition]; - } - - /// Retrieve the last stored payout denominator (the sum of all numerators of the oracle). - /// @param _oracleId The oracle ID. - /// The result can't change if the oracle has finished. - function payoutDenominator(uint64 _oracleId) public view returns (uint256) { - return payoutDenominatorMap[_oracleId]; - } - - /// Called by the oracle owner for reporting results of conditions. - /// @param _oracleId The oracle ID. - /// @param _condition The condition. - /// @param _numerator The relative score of the condition. - /// Note: We could make oracles easily verificable by a hash of all the data, but - /// - It may need allowing to set a numerator only once. - /// - It may be not necessary because future technology will allow to aggregate blockchains. - function reportNumerator(uint64 _oracleId, uint256 _condition, uint256 _numerator) external - _isOracle(_oracleId) - _oracleNotFinished(_oracleId) // otherwise an oracle can break data consistency - { - _updateNumerator(_oracleId, _numerator, _condition); - emit ReportedNumerator(_oracleId, _condition, _numerator); - } - - /// Called by the oracle owner for reporting results of several conditions. - /// @param _oracleId The oracle ID. - /// @param _conditions The conditions. - /// @param _numerators The relative scores of the condition. - function reportNumeratorsBatch(uint64 _oracleId, uint64[] calldata _conditions, uint256[] calldata _numerators) external - _isOracle(_oracleId) - _oracleNotFinished(_oracleId) // otherwise an oracle can break data consistency - { - require(_conditions.length == _numerators.length, "Length mismatch."); - for (uint _i = 0; _i < _conditions.length; ++_i) { - _updateNumerator(_oracleId, _numerators[_i], _conditions[_i]); - } - emit ReportedNumeratorsBatch(_oracleId, _conditions, _numerators); - } - - /// Need to be called after all numerators were reported. - /// @param _oracleId The oracle ID. - /// - /// You should set grace period end time before calling this method. - /// - /// TODO: Maybe it makes sense to allow to set finish time in a point of the future? - function finishOracle(uint64 _oracleId) external - _isOracle(_oracleId) - { - oracleFinishedMap[_oracleId] = true; - emit OracleFinished(_oracleId); - } - - /// Check if an oracle has finished. - /// @param _oracleId The oracle ID. - /// @return `true` if it has finished. - function isOracleFinished(uint64 _oracleId) public view override returns (bool) { - return oracleFinishedMap[_oracleId]; - } - - function _updateNumerator(uint64 _oracleId, uint256 _numerator, uint256 _condition) private { - payoutDenominatorMap[_oracleId] = payoutDenominatorMap[_oracleId].add(_numerator).sub(payoutNumeratorsMap[_oracleId][_condition]); - payoutNumeratorsMap[_oracleId][_condition] = _numerator; - } - - // Virtuals // - - function _calcRewardShare(uint64 _oracleId, uint256 _condition) internal virtual override view returns (int128) { - uint256 _numerator = payoutNumeratorsMap[_oracleId][_condition]; - uint256 _denominator = payoutDenominatorMap[_oracleId]; - return ABDKMath64x64.divu(_numerator, _denominator); - } - - // Modifiers // - - modifier _oracleNotFinished(uint64 _oracleId) { - require(!isOracleFinished(_oracleId), "Oracle is finished."); - _; - } -} diff --git a/assets/eip-3267/contracts/BaseLock.sol b/assets/eip-3267/contracts/BaseLock.sol deleted file mode 100644 index 7e45627..0000000 --- a/assets/eip-3267/contracts/BaseLock.sol +++ /dev/null @@ -1,510 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; -import { ABDKMath64x64 } from "abdk-libraries-solidity/ABDKMath64x64.sol"; -import { ERC1155WithTotals } from "./ERC1155/ERC1155WithTotals.sol"; -import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/ERC1155Holder.sol"; -import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/ERC721Holder.sol"; - -/// @title A base class to lock collaterals and distribute them proportional to an oracle result. -/// @author Victor Porton -/// @notice Not audited, not enough tested. -/// -/// One can also donate/bequest a smart wallet (explain how). -/// -/// We have two kinds of ERC-1155 token IDs: -/// - conditional tokens: numbers < 2**64 -/// - a combination of a collateral contract address and collateral token ID -/// (a counter of donated amount of collateral tokens, don't confuse with collateral tokens themselves) -/// -/// Inheriting from here don't forget to create `createOracle()` external method. -abstract contract BaseLock is - ERC1155WithTotals, - ERC1155Holder, // You are recommended to use `donate()` function instead. - ERC721Holder // It can be used through an ERC-1155 wrapper. -{ - using ABDKMath64x64 for int128; - using SafeMath for uint256; - - /// Emitted when an oracle is created. - /// @param oracleId The ID of the created oracle. - event OracleCreated(uint64 oracleId); - - /// Emitted when an oracle owner is set. - /// @param oracleOwner Who created an oracle - /// @param oracleId The ID of the oracle. - event OracleOwnerChanged(address indexed oracleOwner, uint64 indexed oracleId); - - /// Emitted when an oracle owner is set. - /// @param sender Who created the condition - /// @param customer The owner of the condition. - /// @param condition The created condition ID. - event ConditionCreated(address indexed sender, address indexed customer, uint256 indexed condition); - - /// Emitted when a collateral is donated. - /// @param collateralContractAddress The ERC-1155 contract of the donated token. - /// @param collateralTokenId The ERC-1155 ID of the donated token. - /// @param sender Who donated. - /// @param amount The amount donated. - /// @param to Whose account the donation is assigned to. - /// @param data Additional transaction data. - event DonateCollateral( - IERC1155 indexed collateralContractAddress, - uint256 indexed collateralTokenId, - address indexed sender, - uint256 amount, - address to, - bytes data - ); - - /// Emitted when an oracle is marked as having finished its work. - /// @param oracleId The oracle ID. - event OracleFinished(uint64 indexed oracleId); - - /// Emitted when collateral is withdrawn. - /// @param contractAddress The ERC-1155 contract of the collateral token. - /// @param collateralTokenId The ERC-1155 token ID of the collateral. - /// @param oracleId The oracle ID for which withdrawal is done. - /// @param user Who has withdrawn. - /// @param amount The amount withdrawn. - event CollateralWithdrawn( - IERC1155 indexed contractAddress, - uint256 indexed collateralTokenId, - uint64 indexed oracleId, - address user, - uint256 amount - ); - - // Next ID. - uint64 public maxOracleId; // It doesn't really need to be public. - uint64 public maxConditionId; // It doesn't really need to be public. - - // Mapping (oracleId => oracle owner). - mapping(uint64 => address) private oracleOwnersMap; - // Mapping (oracleId => time) the max time for first withdrawal. - mapping(uint64 => uint) private gracePeriodEnds; - // The user lost the right to transfer conditional tokens: (user => (conditionalToken => bool)). - mapping(address => mapping(uint256 => bool)) private userUsedRedeemMap; - // Mapping (token => (original user => amount)) used to calculate withdrawal of collateral amounts. - mapping(uint256 => mapping(address => uint256)) public lastCollateralBalanceFirstRoundMap; - // Mapping (token => (original user => amount)) used to calculate withdrawal of collateral amounts. - mapping(uint256 => mapping(address => uint256)) public lastCollateralBalanceSecondRoundMap; - /// Mapping (oracleId => amount user withdrew in first round) (see `docs/Calculations.md`). - mapping(uint64 => uint256) public usersWithdrewInFirstRound; - - // Mapping (condition ID => original account) - mapping(uint256 => address) public conditionOwners; - - /// Constructor. - /// @param _uri Our ERC-1155 tokens description URI. - constructor(string memory _uri) ERC1155WithTotals(_uri) { - _registerInterface( - BaseLock(0).onERC1155Received.selector ^ - BaseLock(0).onERC1155BatchReceived.selector ^ - BaseLock(0).onERC721Received.selector - ); - } - - /// This function makes no sense, because it would produce a condition with zero tokens. - // function createCondition() public returns (uint64) { - // return _createCondition(); - // } - - /// Modify the owner of an oracle. - /// @param _newOracleOwner New owner. - /// @param _oracleId The oracle whose owner to change. - function changeOracleOwner(address _newOracleOwner, uint64 _oracleId) public _isOracle(_oracleId) { - oracleOwnersMap[_oracleId] = _newOracleOwner; - emit OracleOwnerChanged(_newOracleOwner, _oracleId); - } - - /// Set the end time of the grace period. - /// - /// The first withdrawal can be done *only* during the grace period. - /// The second withdrawal can be done after the end of the grace period and only if the first withdrawal was done. - /// - /// The intention of the grace period is to check which of users are active ("alive"). - function updateGracePeriodEnds(uint64 _oracleId, uint _time) public _isOracle(_oracleId) { - gracePeriodEnds[_oracleId] = _time; - } - - /// Donate funds in an ERC-1155 token. - /// - /// First, the collateral token need to be approved to be spent by this contract from the address `_from`. - /// - /// It also mints a token (with a different ID), that counts donations in that token. - /// - /// @param _collateralContractAddress The collateral ERC-1155 contract address. - /// @param _collateralTokenId The collateral ERC-1155 token ID. - /// @param _oracleId The oracle ID to whose ecosystem to donate to. - /// @param _amount The amount to donate. - /// @param _from From whom to take the donation. - /// @param _to On whose account the donation amount is assigned. - /// @param _data Additional transaction data. - function donate( - IERC1155 _collateralContractAddress, - uint256 _collateralTokenId, - uint64 _oracleId, - uint256 _amount, - address _from, - address _to, - bytes calldata _data) public - { - uint _donatedPerOracleCollateralTokenId = _collateralDonatedPerOracleTokenId(_collateralContractAddress, _collateralTokenId, _oracleId); - _mint(_to, _donatedPerOracleCollateralTokenId, _amount, _data); - uint _donatedCollateralTokenId = _collateralDonatedTokenId(_collateralContractAddress, _collateralTokenId); - _mint(_to, _donatedCollateralTokenId, _amount, _data); - emit DonateCollateral(_collateralContractAddress, _collateralTokenId, _from, _amount, _to, _data); - _collateralContractAddress.safeTransferFrom(_from, address(this), _collateralTokenId, _amount, _data); // last against reentrancy attack - } - - /// Gather a DeFi profit of a token previous donated to this contact. - /// @param _collateralContractAddress The collateral ERC-1155 contract address. - /// @param _collateralTokenId The collateral ERC-1155 token ID. - /// @param _oracleId The oracle ID to whose ecosystem to donate to. - /// @param _data Additional transaction data. - /// TODO: Batch calls in several tokens and/or to several oracles for less gas usage? - function gatherDeFiProfit( - IERC1155 _collateralContractAddress, - uint256 _collateralTokenId, - uint64 _oracleId, - bytes calldata _data) external - { - uint _donatedPerOracleCollateralTokenId = _collateralDonatedPerOracleTokenId(_collateralContractAddress, _collateralTokenId, _oracleId); - uint _donatedCollateralTokenId = _collateralDonatedTokenId(_collateralContractAddress, _collateralTokenId); - - // We consider an overflow an error and just revert: - // FIXME: Impossible due to reentrancy vulnerability? (Really? It's a view!) - uint256 _difference = - _collateralContractAddress.balanceOf(address(this), _collateralTokenId).sub( - balanceOf(address(this), _donatedCollateralTokenId)); - uint256 _amount = // rounding down to prevent overflows - _difference * - balanceOf(address(this), _donatedPerOracleCollateralTokenId) / - balanceOf(address(this), _donatedCollateralTokenId); - - // Last to avoid reentrancy vulnerability. - donate( - _collateralContractAddress, - _collateralTokenId, - _oracleId, - _amount, - address(this), - address(this), - _data); - } - - /// Calculate how much collateral is owed to a user. - /// @param _collateralContractAddress The ERC-1155 collateral token contract. - /// @param _collateralTokenId The ERC-1155 collateral token ID. - /// @param _oracleId From which oracle's "account" to withdraw. - /// @param _condition The condition (the original receiver of a conditional token). - /// @param _user The user to which we may owe. - function collateralOwing( - IERC1155 _collateralContractAddress, - uint256 _collateralTokenId, - uint64 _oracleId, - uint256 _condition, - address _user - ) external view returns(uint256) { - bool _inFirstRound = _isInFirstRound(_oracleId); - (, uint256 _donated) = - _collateralOwingBase(_collateralContractAddress, _collateralTokenId, _oracleId, _condition, _user, _inFirstRound); - return _donated; - } - - /// Transfer to `msg.sender` the collateral ERC-1155 token. - /// - /// The amount transferred is proportional to the score of `_condition` by the oracle. - /// @param _collateralContractAddress The ERC-1155 collateral token contract. - /// @param _collateralTokenId The ERC-1155 collateral token ID. - /// @param _oracleId From which oracle's "account" to withdraw. - /// @param _condition The condition. - /// @param _data Additional data. - /// - /// Notes: - /// - It is made impossible to withdraw somebody's other collateral, as otherwise we can't mark non-active - /// accounts in grace period. - /// - We can't transfer to somebody other than `msg.sender` because anybody can transfer - /// (needed for multi-level transfers). - /// - After this function is called, it becomes impossible to transfer the corresponding conditional token - /// of `msg.sender` (to prevent its repeated withdrawal). - function withdrawCollateral( - IERC1155 _collateralContractAddress, - uint256 _collateralTokenId, - uint64 _oracleId, - uint256 _condition, - bytes calldata _data) external - { - require(isOracleFinished(_oracleId), "too early"); // to prevent the denominator or the numerators change meantime - bool _inFirstRound = _isInFirstRound(_oracleId); - userUsedRedeemMap[msg.sender][_condition] = true; - // _burn(msg.sender, _condition, conditionalBalance); // Burning it would break using the same token for multiple markets. - (uint _donatedPerOracleCollateralTokenId, uint256 _owingDonated) = - _collateralOwingBase(_collateralContractAddress, _collateralTokenId, _oracleId, _condition, msg.sender, _inFirstRound); - - // Against rounding errors. Not necessary because of rounding down. - // if(_owing > balanceOf(address(this), _collateralTokenId)) _owing = balanceOf(address(this), _collateralTokenId); - - if (_owingDonated != 0) { - uint256 _newTotal = totalSupply(_donatedPerOracleCollateralTokenId); - if (_inFirstRound) { - lastCollateralBalanceFirstRoundMap[_donatedPerOracleCollateralTokenId][msg.sender] = _newTotal; - } else { - lastCollateralBalanceSecondRoundMap[_donatedPerOracleCollateralTokenId][msg.sender] = _newTotal; - } - } - if (!_inFirstRound) { - usersWithdrewInFirstRound[_oracleId] = usersWithdrewInFirstRound[_oracleId].add(_owingDonated); - } - // Last to prevent reentrancy attack: - _collateralContractAddress.safeTransferFrom(address(this), msg.sender, _collateralTokenId, _owingDonated, _data); - emit CollateralWithdrawn( - _collateralContractAddress, - _collateralTokenId, - _oracleId, - msg.sender, - _owingDonated - ); - } - - /// An ERC-1155 function. - /// - /// We disallow transfers of conditional tokens after redeem `_to` prevent "gathering" them before redeeming - /// each oracle. - function safeTransferFrom( - address _from, - address _to, - uint256 _id, - uint256 _value, - bytes calldata _data - ) - public override - { - _checkTransferAllowed(_id, _from); - _baseSafeTransferFrom(_from, _to, _id, _value, _data); - } - - /// An ERC-1155 function. - /// - /// We disallow transfers of conditional tokens after redeem `_to` prevent "gathering" them before redeeming - /// each oracle. - function safeBatchTransferFrom( - address _from, - address _to, - uint256[] calldata _ids, - uint256[] calldata _values, - bytes calldata _data - ) - public override - { - for(uint _i = 0; _i < _ids.length; ++_i) { - _checkTransferAllowed(_ids[_i], _from); - } - _baseSafeBatchTransferFrom(_from, _to, _ids, _values, _data); - } - - // Getters // - - /// Get the oracle owner. - /// @param _oracleId The oracle ID. - function oracleOwner(uint64 _oracleId) public view returns (address) { - return oracleOwnersMap[_oracleId]; - } - - /// Is the oracle marked as having finished its work? - /// - /// `oracleId` is the oracle ID. - function isOracleFinished(uint64 /*oracleId*/) public virtual view returns (bool) { - return true; - } - - /// Are transfers of a conditinal token locked? - /// - /// This is used to prevent its repeated withdrawal. - /// @param _user Querying if locked for this user. - /// @param _condition The condition (the original receiver of a conditional token). - function isConditionalLocked(address _user, uint256 _condition) public view returns (bool) { - return userUsedRedeemMap[_user][_condition]; - } - - /// Retrieve the end of the grace period. - /// @param _oracleId For which oracle. - function gracePeriodEnd(uint64 _oracleId) public view returns (uint) { - return gracePeriodEnds[_oracleId]; - } - - // Virtual functions // - - /// Current address of a user. - /// @param _originalAddress The original address of the user. - function originalToCurrentAddress(address _originalAddress) internal virtual returns (address) { - return _originalAddress; - } - - /// Mint a conditional to a customer. - function _mintToCustomer(address _customer, uint256 _condition, uint256 _amount, bytes calldata _data) - internal virtual - { - require(conditionOwners[_condition] == _customer, "Other's salary get attempt."); - _mint(originalToCurrentAddress(_customer), _condition, _amount, _data); - } - - /// Calculate the share of a condition in an oracle's market. - /// @param _oracleId The oracle ID. - /// @return Uses `ABDKMath64x64` number ID. - function _calcRewardShare(uint64 _oracleId, uint256 _condition) internal virtual view returns (int128); - - function _calcMultiplier(uint64 _oracleId, uint256 _condition, int128 _oracleShare) internal virtual view returns (int128) { - int128 _rewardShare = _calcRewardShare(_oracleId, _condition); - return _oracleShare.mul(_rewardShare); - } - - function _doTransfer(uint256 _id, address _from, address _to, uint256 _value) internal virtual { - _balances[_id][_from] = _balances[_id][_from].sub(_value); - _balances[_id][_to] = _value.add(_balances[_id][_to]); - } - - // Internal // - - /// Generate the ERC-1155 token ID that counts amount of donations per oracle for a ERC-1155 collateral token. - /// @param _collateralContractAddress The ERC-1155 contract of the collateral token. - /// @param _collateralTokenId The ERC-1155 ID of the collateral token. - /// @param _oracleId The oracle ID. - /// Note: It does not conflict with other tokens kinds, because the only other one is the uint64 conditional. - function _collateralDonatedPerOracleTokenId(IERC1155 _collateralContractAddress, uint256 _collateralTokenId, uint64 _oracleId) - internal pure returns (uint256) - { - return uint256(keccak256(abi.encodePacked(_collateralContractAddress, _collateralTokenId, _oracleId))); - } - - /// Generate the ERC-1155 token ID that counts amount of donations for a ERC-1155 collateral token. - /// @param _collateralContractAddress The ERC-1155 contract of the collateral token. - /// @param _collateralTokenId The ERC-1155 ID of the collateral token. - /// Note: It does not conflict with other tokens kinds, because the only other one is the uint64 conditional. - function _collateralDonatedTokenId(IERC1155 _collateralContractAddress, uint256 _collateralTokenId) - internal pure returns (uint256) - { - return uint256(keccak256(abi.encodePacked(_collateralContractAddress, _collateralTokenId))); - } - - function _checkTransferAllowed(uint256 _id, address _from) internal view { - require(!userUsedRedeemMap[_from][_id], "You can't trade conditional tokens after redeem."); - } - - function _baseSafeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes memory _data) private { - require(_to != address(0), "ERC1155: target address must be non-zero"); - require( - _from == msg.sender || _operatorApprovals[_from][msg.sender] == true, - "ERC1155: need operator approval for 3rd party transfers." - ); - - _doTransfer(_id, _from, _to, _value); - - emit TransferSingle(msg.sender, _from, _to, _id, _value); - - _doSafeTransferAcceptanceCheck(msg.sender, _from, _to, _id, _value, _data); - } - - function _baseSafeBatchTransferFrom( - address _from, - address _to, - uint256[] memory _ids, - uint256[] memory _values, - bytes memory _data - ) - private - { - require(_ids.length == _values.length, "ERC1155: IDs and _values must have same lengths"); - require(_to != address(0), "ERC1155: target address must be non-zero"); - require( - _from == msg.sender || _operatorApprovals[_from][msg.sender] == true, - "ERC1155: need operator approval for 3rd party transfers." - ); - - for (uint256 _i = 0; _i < _ids.length; ++_i) { - uint256 _id = _ids[_i]; - uint256 _value = _values[_i]; - - _doTransfer(_id, _from, _to, _value); - } - - emit TransferBatch(msg.sender, _from, _to, _ids, _values); - - _doSafeBatchTransferAcceptanceCheck(msg.sender, _from, _to, _ids, _values, _data); - } - - function _createOracle() internal returns (uint64) { - uint64 _oracleId = ++maxOracleId; - oracleOwnersMap[_oracleId] = msg.sender; - emit OracleCreated(_oracleId); - emit OracleOwnerChanged(msg.sender, _oracleId); - return _oracleId; - } - - /// Start with 1, not 0, to avoid glitch with `conditionalTokens` variable. - /// - /// TODO: Use uint64 variables instead? - function _createCondition(address _customer) internal returns (uint256) { - return _doCreateCondition(_customer); - } - - /// Start with 1, not 0, to avoid glitch with `conditionalTokens` variable. - /// - /// TODO: Use uint64 variables instead? - function _doCreateCondition(address _customer) internal virtual returns (uint256) { - uint64 _condition = ++maxConditionId; - - conditionOwners[_condition] = _customer; - - emit ConditionCreated(msg.sender, _customer, _condition); - - return _condition; - } - - function _collateralOwingBase( - IERC1155 _collateralContractAddress, - uint256 _collateralTokenId, - uint64 _oracleId, - uint256 _condition, - address _user, - bool _inFirstRound - ) - private view returns (uint _donatedPerOracleCollateralTokenId, uint256 _donated) - { - uint256 _conditionalBalance = balanceOf(_user, _condition); - uint256 _totalConditionalBalance = - _inFirstRound ? totalSupply(_condition) : usersWithdrewInFirstRound[_oracleId]; - _donatedPerOracleCollateralTokenId = _collateralDonatedPerOracleTokenId(_collateralContractAddress, _collateralTokenId, _oracleId); - // Rounded to below for no out-of-funds: - int128 _oracleShare = ABDKMath64x64.divu(_conditionalBalance, _totalConditionalBalance); - uint256 _newDividendsDonated = - totalSupply(_donatedPerOracleCollateralTokenId) - - (_inFirstRound - ? lastCollateralBalanceFirstRoundMap[_donatedPerOracleCollateralTokenId][_user] - : lastCollateralBalanceSecondRoundMap[_donatedPerOracleCollateralTokenId][_user]); - int128 _multiplier = _calcMultiplier(_oracleId, _condition, _oracleShare); - _donated = _multiplier.mulu(_newDividendsDonated); - } - - function _isInFirstRound(uint64 _oracleId) internal view returns (bool) { - return block.timestamp <= gracePeriodEnds[_oracleId]; - } - - function _isConditional(uint256 _tokenId) internal pure returns (bool) { - // Zero 2**-192 probability that tokenId < (1<<64) if it's not a conditional. - // Note to auditor: It's a hack, check for no errors carefully. - return _tokenId < (1<<64); - } - - modifier _isOracle(uint64 _oracleId) { - require(oracleOwnersMap[_oracleId] == msg.sender, "Not the oracle owner."); - _; - } - - modifier checkIsConditional(uint256 _tokenId) { - require(_isConditional(_tokenId), "It's not your conditional."); - _; - } -} diff --git a/assets/eip-3267/contracts/BaseRestorableSalary.sol b/assets/eip-3267/contracts/BaseRestorableSalary.sol deleted file mode 100644 index d7eac2d..0000000 --- a/assets/eip-3267/contracts/BaseRestorableSalary.sol +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import "./Salary.sol"; - -/// @author Victor Porton -/// @notice Not audited, not enough tested. -/// A base class for salary with receiver accounts that can be restored by an "attorney". -abstract contract BaseRestorableSalary is BaseSalary { - // INVARIANT: `_originalAddress(newToOldAccount[newAccount]) == _originalAddress(newAccount)` - // if `newToOldAccount[newAccount] != address(0)` for every `newAccount` - // INVARIANT: originalAddresses and originalToCurrentAddresses are mutually inverse. - // That is: - // - `originalAddresses[originalToCurrentAddresses[x]] == x` if `originalToCurrentAddresses[x] != address(0)` - // - `originalToCurrentAddresses[originalAddresses[x]] == x` if `originalAddresses[x] != address(0)` - - /// Mapping (current address => very first address an account had). - mapping(address => address) public originalAddresses; - - /// Mapping (very first address an account had => current address). - mapping(address => address) public originalToCurrentAddresses; - - // Mapping from old to new account addresses (created after every change of an address). - mapping(address => address) public newToOldAccount; - - /// Constructor. - /// @param _uri Our ERC-1155 tokens description URI. - constructor (string memory _uri) BaseSalary(_uri) { } - - /// Below copied from https://github.com/vporton/restorable-funds/blob/f6192fd23cad529b84155d52ae202430cd97db23/contracts/RestorableERC1155.sol - - /// Give the user the "permission" to move funds from `_oldAccount` to `_newAccount`. - /// - /// This function is intended to be called by an attorney or the user to move to a new account. - /// @param _oldAccount is a current address. - /// @param _newAccount is a new address. - function permitRestoreAccount(address _oldAccount, address _newAccount) public { - if (msg.sender != _oldAccount) { - checkAllowedRestoreAccount(_oldAccount, _newAccount); // only authorized "attorneys" or attorney DAOs - } - _avoidZeroAddressManipulatins(_oldAccount, _newAccount); - address _orig = _originalAddress(_oldAccount); - - // We don't disallow joining several accounts together to consolidate salaries for different projects. - // require(originalAddresses[_newAccount] == 0, "Account is taken.") - - newToOldAccount[_newAccount] = _oldAccount; - originalAddresses[_newAccount] = _orig; - originalToCurrentAddresses[_orig] = _newAccount; - // Auditor: Check that the above invariant hold. - emit AccountRestored(_oldAccount, _newAccount); - } - - /// Retire the user's "permission" to move funds from `_oldAccount` to `_newAccount`. - /// - /// This function is intended to be called by an attorney. - /// @param _oldAccount is an old current address. - /// @param _newAccount is a new address. - /// (In general) it isn't allowed to be called by `msg.sender == _oldAccount`, - /// because it would allow to keep stealing the salary by hijacked old account. - function dispermitRestoreAccount(address _oldAccount, address _newAccount) public { - checkAllowedUnrestoreAccount(_oldAccount, _newAccount); // only authorized "attorneys" or attorney DAOs - _avoidZeroAddressManipulatins(_oldAccount, _newAccount); - newToOldAccount[_newAccount] = address(0); - originalToCurrentAddresses[_oldAccount] = address(0); - originalAddresses[_newAccount] = address(0); - // Auditor: Check that the above invariants hold. - emit AccountUnrestored(_oldAccount, _newAccount); - } - - /// Move the entire balance of a token from an old account to a new account of the same user. - /// @param _oldAccount Old account. - /// @param _newAccount New account. - /// @param _token The ERC-1155 token ID. - /// This function can be called by the affected user. - /// - /// Remark: We don't need to create new tokens like as on a regular transfer, - /// because it isn't a transfer to a trader. - function restoreFunds(address _oldAccount, address _newAccount, uint256 _token) public - checkMovedOwner(_oldAccount, _newAccount) - { - uint256 _amount = _balances[_token][_oldAccount]; - - _balances[_token][_newAccount] = _balances[_token][_oldAccount]; - _balances[_token][_oldAccount] = 0; - - emit TransferSingle(_msgSender(), _oldAccount, _newAccount, _token, _amount); - } - - /// Move the entire balance of tokens from an old account to a new account of the same user. - /// @param _oldAccount Old account. - /// @param _newAccount New account. - /// @param _tokens The ERC-1155 token IDs. - /// This function can be called by the affected user. - /// - /// Remark: We don't need to create new tokens like as on a regular transfer, - /// because it isn't a transfer to a trader. - function restoreFundsBatch(address _oldAccount, address _newAccount, uint256[] calldata _tokens) public - checkMovedOwner(_oldAccount, _newAccount) - { - uint256[] memory _amounts = new uint256[](_tokens.length); - for (uint _i = 0; _i < _tokens.length; ++_i) { - uint256 _token = _tokens[_i]; - uint256 _amount = _balances[_token][_oldAccount]; - _amounts[_i] = _amount; - - _balances[_token][_newAccount] = _balances[_token][_oldAccount]; - _balances[_token][_oldAccount] = 0; - } - - emit TransferBatch(_msgSender(), _oldAccount, _newAccount, _tokens, _amounts); - } - - // Internal functions // - - function _avoidZeroAddressManipulatins(address _oldAccount, address _newAccount) internal view { - // To avoid make-rich-quick manipulations with lost funds: - require(_oldAccount != address(0) && _newAccount != address(0) && - originalAddresses[_newAccount] != address(0) && newToOldAccount[_newAccount] != address(0), - "Trying to get nobody's funds."); - } - - // Virtual functions // - - /// Check if `msg.sender` is an attorney allowed to restore a user's account. - function checkAllowedRestoreAccount(address /*_oldAccount*/, address /*_newAccount*/) public virtual; - - /// Check if `msg.sender` is an attorney allowed to unrestore a user's account. - function checkAllowedUnrestoreAccount(address /*_oldAccount*/, address /*_newAccount*/) public virtual; - - /// Find the original address of a given account. - /// This function is internal, because it can be calculated off-chain. - /// @param _account The current address. - function _originalAddress(address _account) internal view virtual returns (address) { - address _newAddress = originalAddresses[_account]; - return _newAddress != address(0) ? _newAddress : _account; - } - - // Find the current address for an original address. - // @param _conditional The original address. - function originalToCurrentAddress(address _customer) internal virtual override returns (address) { - address current = originalToCurrentAddresses[_customer]; - return current != address(0) ? current : _customer; - } - - // TODO: Is the following function useful to save gas in other contracts? - // function getCurrent(address _account) public returns (uint256) { - // address _original = originalAddresses[_account]; - // return _original == 0 ? 0 : originalToCurrentAddress(_original); - // } - - // Modifiers // - - /// Check that `_newAccount` is the user that has the right to restore funds from `_oldAccount`. - /// - /// We also allow funds restoration by attorneys for convenience of users. - /// This is not an increased security risk, because a dishonest attorney can anyway transfer money to himself. - modifier checkMovedOwner(address _oldAccount, address _newAccount) virtual { - if (_msgSender() != _newAccount) { - checkAllowedRestoreAccount(_oldAccount, _newAccount); // only authorized "attorneys" or attorney DAOs - } - - for (address _account = _oldAccount; _account != _newAccount; _account = newToOldAccount[_account]) { - require(_account != address(0), "Not a moved owner"); - } - - _; - } - - // Events // - - event AccountRestored(address indexed oldAccount, address indexed newAccount); - - event AccountUnrestored(address indexed oldAccount, address indexed newAccount); -} diff --git a/assets/eip-3267/contracts/BaseSalary.sol b/assets/eip-3267/contracts/BaseSalary.sol deleted file mode 100644 index a695f53..0000000 --- a/assets/eip-3267/contracts/BaseSalary.sol +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import "./BaseBidOnAddresses.sol"; - -/// @title Base class for a "salary" that is paid one token per second using minted conditionals. -/// @author Victor Porton -/// @notice Not audited, not enough tested. -/// It was considered to allow the DAO to adjust registration date to pay salary retrospectively, -/// but this seems giving too much rights to the DAO similarly as if it had the right to declare anyone dead. -/// -/// It would cause this effect: A scientist who is already great may register then his date is moved back -/// in time and instantly he or she receives a very big sum of money to his account. -/// If it is done erroneously, there may be no way to move the registration date again forward in time, -/// because the tokens may be already withdrawn. And it cannot be done in a fully decentralized way because -/// it needs oracles. So errors are seem inevitable. -/// On the other hand, somebody malicious may create and register in my system a pool of Ethereum addresses that -/// individuals can receive from them as if they themselves registered in the past. -/// So it in some cases (if the registration date is past the contract deployment) this issue is impossible to -/// mitigate. -/// -/// The salary is paid in minted tokens groups into "chains": -/// the original salary token and anyone can replace it by another token, next in the chain. -contract BaseSalary is BaseBidOnAddresses { - /// Salary receiver registered. - /// @param customer The customer address. - /// @param oracleId The oracle ID for which he registers. - /// @param data Additional data. - event CustomerRegistered( - address indexed customer, - uint64 indexed oracleId, - uint256 indexed condition, - bytes data - ); - - /// Salary tokens minted. - /// @param customer The customer address. - /// @param oracleId The oracle ID. - /// @param amount The minted amount. - /// @param data Additional data. - event SalaryMinted( - address indexed customer, - uint64 indexed oracleId, - uint256 indexed condition, - uint256 amount, - bytes data - ); - - /// Salary token recreated (salary recalculation request). - /// @param customer The customer address. - /// @param originalCondition The original token ID. - /// @param newCondition The new token ID. - event ConditionReCreate( - address indexed customer, - uint256 indexed originalCondition, - uint256 indexed newCondition - ); - - // Mapping (condition ID => registration time). - mapping(uint256 => uint) public conditionCreationDates; - // Mapping (condition ID => salary block time). - mapping(uint256 => uint) public lastSalaryDates; - /// Mapping (condition ID => account) - salary recipients. - mapping(uint256 => address) public salaryReceivers; - - /// Mapping (condition ID => first condition ID in the chain) - /// - /// I call _chain_ of conditions the list of conditions resulting from creating and recreating conditions. - mapping(uint256 => uint256) public firstConditionInChain; - /// Mapping (first condition ID in the chain => last condition ID in the chain) - /// - /// I call _chain_ of conditions the list of conditions resulting from creating and recreating conditions. - mapping(uint256 => uint256) public firstToLastConditionInChain; - - /// Constructor. - /// @param _uri The ERC-1155 token URI. - constructor(string memory _uri) BaseBidOnAddresses(_uri) { } - - /// Mint a salary token. - /// @param _oracleId The oracle ID. - /// @param _condition The condition ID. - /// @param _data Additional data. - /// This method can be called only by the salary receiver. - function mintSalary(uint64 _oracleId, uint256 _condition, bytes calldata _data) - ensureLastConditionInChain(_condition) external - { - uint _lastSalaryDate = lastSalaryDates[_condition]; - require(_lastSalaryDate != 0, "You are not registered."); - // Note: Even if you withdraw once per 20 years, you will get only 630,720,000 tokens. - // This number is probably not to big to be displayed well in UIs. - uint256 _amount = (block.timestamp - _lastSalaryDate) * 10**18; // one token per second - _mintToCustomer(msg.sender, firstToLastConditionInChain[_condition], _amount, _data); - lastSalaryDates[_condition] = block.timestamp; - emit SalaryMinted(msg.sender, _oracleId, _condition, _amount, _data); - } - - /// Make a new condition that replaces the old one. - /// - /// In other words, it is a request to recalculate somebody's salary. - /// - /// Anyone can request to recalculate anyone's salary. - /// - /// It's also useful to punish someone for decreasing his work performance or an evil act. - /// This is to be called among other when a person dies. - /// - /// Recalculation is also forced when a salary receiver transfers away his current salary token. - /// It is useful to remove a trader's incentive to kill the issuer to reduce the circulating supply. - /// - /// Issue to solve later: Should we recommend: - /// - calling this function on each new project milestone? - /// - calling this function regularly (e.g. every week)? - /// - /// This function also withdraws the old token. - function recreateCondition(uint256 _condition) public returns (uint256) { - return _recreateCondition(_condition); - } - - function _doCreateCondition(address _customer) internal virtual override returns (uint256) { - uint256 _condition = super._doCreateCondition(_customer); - salaryReceivers[_condition] = _customer; - conditionCreationDates[_condition] = block.timestamp; - firstConditionInChain[_condition] = _condition; - firstToLastConditionInChain[_condition] = _condition; - return _condition; - } - - /// Make a new condition that replaces the old one. - /// The same can be done by transferring to yourself 0 tokens, but this method uses less gas. - /// - /// We need to create a new condition every time when an outgoimg transfer of a conditional token happens. - /// Otherwise an investor would gain if he kills a scientist to reduce the circulating supply of his token to increase the price. - /// Allowing old tokens to be exchangeable for new ones? (Allowing the reverse swap would create killer's gain.) - /// Additional benefit of this solution: We can have different rewards at different stages of project, - /// what may be beneficial for early startups funding. - /// - /// Problem to be solved later: There should be an advice to switch to a new token at each milestone of a project? - /// - /// Anyone can create a ERC-1155 contract that allows to use any of the tokens in the list - /// by locking any of the tokens in the list as a new "general" token. We should recommend customers not to - /// use such contracts, because it creates for them the killer exploit. - /// - /// If we would exchange the old and new tokens for the same amounts of collaterals, then it would be - /// effectively the same token and therefore minting more new token would possibly devalue the old one, - /// thus triggering the killer's exploit again. So we make old and new completely independent. - /// - /// Old token is 1:1 converted to the new token. - /// - /// Remark: To make easy to exchange the token even if it is recreated, we can make a wrapper or locker - /// token that uses `firstConditionInChain[]` to aggregate several tokens together. - /// A similar wrapper (the customer need to `setApprovalForAll()` on it) that uses - /// `firstToLastConditionInChain[]` can be used to transfer away recreated tokens - /// even if an evil DAO tries to frontrun the customer by recreating his tokens very often. - /// TODO: Test that it's possible to create such a locker. - /// - /// Note: That wrapper could be carelessly used to create the investor's killing customer incentive - /// by the customer using it to transfer to an investor. Even if the customer uses it only for - /// exchanges, an investor can buy at an exchange and be a killer. - /// To make it safe, it must stop accepting any new tokens after a transfer. - /// It can determine if a token is new just comparing by `<` operator. - /// It's strongly recommended that an app that uses this contract provides its own swap/exchange UI - /// and warns the user not to use arbitrary exchanges as being an incentive to kill the user. - /// - /// We allow anybody (not just the account owner or DAO) to recreate a condition, because: - /// - Exchanges can create a "composite" token that allows to withdraw any of the tokens in the chain - /// up to a certain period of time (using on-chain `conditionCreationDates`). - /// - Therefore somebody's token can be withdrawn even if its ID changes arbitrarily often. - /// - /// @param _condition The condition ID. - function _recreateCondition(uint256 _condition) - internal ensureFirstConditionInChain(_condition) returns (uint256) - { - address _customer = salaryReceivers[_condition]; - uint256 _oldCondition = firstToLastConditionInChain[_condition]; - uint256 _newCondition = _doCreateCondition(_customer); - firstConditionInChain[_newCondition] = _condition; - - uint256 _amount = _balances[_oldCondition][_customer]; - _balances[_newCondition][_customer] = _amount; - _balances[_oldCondition][_customer] = 0; - - // TODO: Should we swap two following lines? - emit TransferSingle(msg.sender, _customer, address(0), _condition, _amount); - emit TransferSingle(msg.sender, address(0), _customer, _newCondition, _amount); - - lastSalaryDates[_newCondition] = lastSalaryDates[_condition]; - // TODO: Should we here set `lastSalaryDates[_condition] = 0` to save storage space? // TODO: It would also eliminate the need to check in mint function. - - emit ConditionReCreate(_customer, _condition, _newCondition); - return _newCondition; - } - - /// Check if it is the first condition in a chain of conditions. - /// @param _id The condition ID. - /// - /// Must be called with `_id != 0`. - function isFirstConditionInChain(uint256 _id) internal view returns (bool) { - return firstConditionInChain[_id] == _id; - } - - /// Check if it is the last condition in a chain of conditions. - /// @param _id The condition ID. - /// - /// Must be called with `_id != 0`. - /// - /// TODO: Should make this function public? - function isLastConditionInChain(uint256 _id) internal view returns (bool) { - return firstToLastConditionInChain[firstConditionInChain[_id]] == _id; - } - - function _doTransfer(uint256 _id, address _from, address _to, uint256 _value) internal virtual override { - super._doTransfer(_id, _from, _to, _value); - - if (_id != 0 && salaryReceivers[_id] == msg.sender) { - if (isLastConditionInChain(_id)) { // correct because `_id != 0` - _recreateCondition(_id); - } - } - } - - function _registerCustomer(address _customer, uint64 _oracleId, bytes calldata _data) - virtual internal returns (uint256) - { - uint256 _condition = _doCreateCondition(_customer); - lastSalaryDates[_condition] = block.timestamp; - emit CustomerRegistered(msg.sender, _oracleId, _condition, _data); - return _condition; - } - - modifier ensureFirstConditionInChain(uint256 _id) { - // TODO: Is `_id != 0` check needed? - require(_isConditional(_id) && _id != 0 && isFirstConditionInChain(_id), "Only for the last salary token."); - _; - } - - modifier ensureLastConditionInChain(uint256 _id) { - // TODO: Is `_id != 0` check needed? - require(_isConditional(_id) && _id != 0 && isLastConditionInChain(_id), "Only for the last salary token."); - _; - } -} diff --git a/assets/eip-3267/contracts/BidOnAddresses.sol b/assets/eip-3267/contracts/BidOnAddresses.sol deleted file mode 100644 index ec41e77..0000000 --- a/assets/eip-3267/contracts/BidOnAddresses.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import "./BaseBidOnAddresses.sol"; - -/// @title Bidding on Ethereum addresses -/// @author Victor Porton -/// @notice Not audited, not enough tested. -/// This allows anyone to claim 1000 conditional tokens in order for him to transfer money from the future. -/// See `docs/future-money.rst` and anyone to donate. -/// -/// We have two kinds of ERC-1155 token IDs: -/// - conditional tokens: numbers < 2**64 -/// - a combination of a collateral contract address and collateral token ID -/// (a counter of donated amount of collateral tokens, don't confuse with collateral tokens themselves) -/// -/// In functions of this contact `condition` is always a customer's original address. -/// -/// We receive funds in ERC-1155, see also https://github.com/vporton/wrap-tokens -contract BidOnAddresses is BaseBidOnAddresses { - uint constant INITIAL_CUSTOMER_BALANCE = 1000 * 10**18; // an arbitrarily chosen value - - /// Customer registered. - /// @param sender `msg.sender`. - /// @param customer The customer address. - /// @param data Additional data. - event CustomerRegistered( - address indexed sender, - address indexed customer, - uint256 indexed condition, - bytes data - ); - - /// @param _uri The ERC-1155 token URI. - constructor(string memory _uri) BaseBidOnAddresses(_uri) { - _registerInterface( - BidOnAddresses(0).onERC1155Received.selector ^ - BidOnAddresses(0).onERC1155BatchReceived.selector - ); - } - - /// Anyone can register anyone. - /// - /// This can be called both before or after the oracle finish. However registering after the finish is useless. - /// - /// We check that `oracleId` exists (we don't want "spammers" to register themselves for a million oracles). - /// - /// We allow anyone to register anyone. This is useful for being registered by robots. - /// At first it seems to be harmful to make somebody a millionaire unwillingly (he then needs a fortress and bodyguards), - /// but: Salary tokens will be worth real money, only if the registered person publishes his works together - /// with his Ethereum address. So, he can be made rich against his will only by impersonating him. But if somebody - /// impersonates him, then they are able to present him richer than he is anyway, so making him vulnerable to - /// kidnappers anyway. So having somebody registered against his will seems not to be a problem at all - /// (except that he will see superfluous worthless tokens in Etherscan data of his account.) - /// - /// An alternative way would be to make registration gasless but requiring a registrant signature. - /// This is not very good, probably: - /// - It requires to install MetaMask. - /// - It bothers the person to sign something, when he could just be hesitant to get what he needs. - /// - It somehow complicates this contract. - /// @param _customer The address of the customer. // TODO: current or original - /// @param _oracleId The oracle ID. - /// @param _data Additional data. - function registerCustomer(address _customer, uint64 _oracleId, bytes calldata _data) external { - require(_oracleId <= maxOracleId, "Oracle doesn't exist."); - uint256 _condition = _createCondition(_customer); - _mintToCustomer(_customer, _condition, INITIAL_CUSTOMER_BALANCE, _data); - emit CustomerRegistered(msg.sender, _customer, _condition, _data); - } -} diff --git a/assets/eip-3267/contracts/DAOInterface.sol b/assets/eip-3267/contracts/DAOInterface.sol deleted file mode 100644 index c2f15ef..0000000 --- a/assets/eip-3267/contracts/DAOInterface.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; - -/// @notice The "DAO plugin" interface. -/// @author Victor Porton -/// @notice Not audited, not enough tested. -interface DAOInterface { - /// Check if `msg.sender` is an attorney allowed to restore a given account. - function checkAllowedRestoreAccount(address _oldAccount, address _newAccount) external; - - /// Check if `msg.sender` is an attorney allowed to unrestore a given account. - function checkAllowedUnrestoreAccount(address _oldAccount, address _newAccount) external; -} diff --git a/assets/eip-3267/contracts/DefaultDAOInterface.sol b/assets/eip-3267/contracts/DefaultDAOInterface.sol deleted file mode 100644 index 1551455..0000000 --- a/assets/eip-3267/contracts/DefaultDAOInterface.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import "./DAOInterface.sol"; - -/// @notice "Default" contract for `DAOInterface`. -/// @author Victor Porton -/// @notice Not audited, not enough tested. -contract DefaultDAOInterface is DAOInterface { - function checkAllowedRestoreAccount(address /*_oldAccount*/, address /*_newAccount*/) external pure override { - revert("unimplemented"); - } - - function checkAllowedUnrestoreAccount(address /*_oldAccount*/, address /*_newAccount*/) external pure override { - revert("unimplemented"); - } -} diff --git a/assets/eip-3267/contracts/ERC1155/ERC1155.sol b/assets/eip-3267/contracts/ERC1155/ERC1155.sol deleted file mode 100644 index d7f2af5..0000000 --- a/assets/eip-3267/contracts/ERC1155/ERC1155.sol +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.6.0 <0.8.0; - -import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155MetadataURI.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; -import "@openzeppelin/contracts/GSN/Context.sol"; -import "@openzeppelin/contracts/introspection/ERC165.sol"; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -/** - * - * @dev Implementation of the basic standard multi-token. - * See https://eips.ethereum.org/EIPS/eip-1155 - * Originally based on code by Enjin: https://github.com/enjin/erc-1155 - * - * _Available since v3.1._ - */ -contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { - using SafeMath for uint256; - using Address for address; - - // Mapping from token ID to account balances - mapping (uint256 => mapping(address => uint256)) internal _balances; - - // Mapping from account to operator approvals - mapping (address => mapping(address => bool)) internal _operatorApprovals; - - // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json - string private _uri; - - /* - * bytes4(keccak256('balanceOf(address,uint256)')) == 0x00fdd58e - * bytes4(keccak256('balanceOfBatch(address[],uint256[])')) == 0x4e1273f4 - * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 - * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5 - * bytes4(keccak256('safeTransferFrom(address,address,uint256,uint256,bytes)')) == 0xf242432a - * bytes4(keccak256('safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)')) == 0x2eb2c2d6 - * - * => 0x00fdd58e ^ 0x4e1273f4 ^ 0xa22cb465 ^ - * 0xe985e9c5 ^ 0xf242432a ^ 0x2eb2c2d6 == 0xd9b67a26 - */ - bytes4 private constant _INTERFACE_ID_ERC1155 = 0xd9b67a26; - - /* - * bytes4(keccak256('uri(uint256)')) == 0x0e89341c - */ - bytes4 private constant _INTERFACE_ID_ERC1155_METADATA_URI = 0x0e89341c; - - /** - * @dev See {_setURI}. - */ - constructor (string memory uri_) { - _setURI(uri_); - - // register the supported interfaces to conform to ERC1155 via ERC165 - _registerInterface(_INTERFACE_ID_ERC1155); - - // register the supported interfaces to conform to ERC1155MetadataURI via ERC165 - _registerInterface(_INTERFACE_ID_ERC1155_METADATA_URI); - } - - /** - * @dev See {IERC1155MetadataURI-uri}. - * - * This implementation returns the same URI for *all* token types. It relies - * on the token type ID substitution mechanism - * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. - * - * Clients calling this function must replace the `\{id\}` substring with the - * actual token type ID. - */ - function uri(uint256) external view override returns (string memory) { - return _uri; - } - - /** - * @dev See {IERC1155-balanceOf}. - * - * Requirements: - * - * - `account` cannot be the zero address. - */ - function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { - require(account != address(0), "ERC1155: balance query for the zero address"); - return _balances[id][account]; - } - - /** - * @dev See {IERC1155-balanceOfBatch}. - * - * Requirements: - * - * - `accounts` and `ids` must have the same length. - */ - function balanceOfBatch( - address[] memory accounts, - uint256[] memory ids - ) - public - view - virtual - override - returns (uint256[] memory) - { - require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); - - uint256[] memory batchBalances = new uint256[](accounts.length); - - for (uint256 i = 0; i < accounts.length; ++i) { - require(accounts[i] != address(0), "ERC1155: batch balance query for the zero address"); - batchBalances[i] = _balances[ids[i]][accounts[i]]; - } - - return batchBalances; - } - - /** - * @dev See {IERC1155-setApprovalForAll}. - */ - function setApprovalForAll(address operator, bool approved) public virtual override { - require(_msgSender() != operator, "ERC1155: setting approval status for self"); - - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @dev See {IERC1155-isApprovedForAll}. - */ - function isApprovedForAll(address account, address operator) public view virtual override returns (bool) { - return _operatorApprovals[account][operator]; - } - - /** - * @dev See {IERC1155-safeTransferFrom}. - */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) - public - virtual - override - { - require(to != address(0), "ERC1155: transfer to the zero address"); - require( - from == _msgSender() || isApprovedForAll(from, _msgSender()), - "ERC1155: caller is not owner nor approved" - ); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data); - - _balances[id][from] = _balances[id][from].sub(amount, "ERC1155: insufficient balance for transfer"); - _balances[id][to] = _balances[id][to].add(amount); - - emit TransferSingle(operator, from, to, id, amount); - - _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); - } - - /** - * @dev See {IERC1155-safeBatchTransferFrom}. - */ - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) - public - virtual - override - { - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - require(to != address(0), "ERC1155: transfer to the zero address"); - require( - from == _msgSender() || isApprovedForAll(from, _msgSender()), - "ERC1155: transfer caller is not owner nor approved" - ); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, from, to, ids, amounts, data); - - for (uint256 i = 0; i < ids.length; ++i) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - - _balances[id][from] = _balances[id][from].sub( - amount, - "ERC1155: insufficient balance for transfer" - ); - _balances[id][to] = _balances[id][to].add(amount); - } - - emit TransferBatch(operator, from, to, ids, amounts); - - _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); - } - - /** - * @dev Sets a new URI for all token types, by relying on the token type ID - * substitution mechanism - * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. - * - * By this mechanism, any occurrence of the `\{id\}` substring in either the - * URI or any of the amounts in the JSON file at said URI will be replaced by - * clients with the token type ID. - * - * For example, the `https://token-cdn-domain/\{id\}.json` URI would be - * interpreted by clients as - * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` - * for token type ID 0x4cce0. - * - * See {uri}. - * - * Because these URIs cannot be meaningfully represented by the {URI} event, - * this function emits no events. - */ - function _setURI(string memory newuri) internal virtual { - _uri = newuri; - } - - /** - * @dev Creates `amount` tokens of token type `id`, and assigns them to `account`. - * - * Emits a {TransferSingle} event. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the - * acceptance magic value. - */ - function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual { - require(account != address(0), "ERC1155: mint to the zero address"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data); - - _balances[id][account] = _balances[id][account].add(amount); - emit TransferSingle(operator, address(0), account, id, amount); - - _doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data); - } - - /** - * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. - * - * Requirements: - * - * - `ids` and `amounts` must have the same length. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the - * acceptance magic value. - */ - function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual { - require(to != address(0), "ERC1155: mint to the zero address"); - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); - - for (uint i = 0; i < ids.length; i++) { - _balances[ids[i]][to] = amounts[i].add(_balances[ids[i]][to]); - } - - emit TransferBatch(operator, address(0), to, ids, amounts); - - _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); - } - - /** - * @dev Destroys `amount` tokens of token type `id` from `account` - * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have at least `amount` tokens of token type `id`. - */ - function _burn(address account, uint256 id, uint256 amount) internal virtual { - require(account != address(0), "ERC1155: burn from the zero address"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, account, address(0), _asSingletonArray(id), _asSingletonArray(amount), ""); - - _balances[id][account] = _balances[id][account].sub( - amount, - "ERC1155: burn amount exceeds balance" - ); - - emit TransferSingle(operator, account, address(0), id, amount); - } - - /** - * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. - * - * Requirements: - * - * - `ids` and `amounts` must have the same length. - */ - function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual { - require(account != address(0), "ERC1155: burn from the zero address"); - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - - for (uint i = 0; i < ids.length; i++) { - _balances[ids[i]][account] = _balances[ids[i]][account].sub( - amounts[i], - "ERC1155: burn amount exceeds balance" - ); - } - - emit TransferBatch(operator, account, address(0), ids, amounts); - } - - /** - * @dev Hook that is called before any token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - `ids` and `amounts` have the same, non-zero length. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) - internal virtual - { } - - function _doSafeTransferAcceptanceCheck( - address operator, - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) - internal - { - if (to.isContract()) { - try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { - if (response != IERC1155Receiver(to).onERC1155Received.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); - } - } catch Error(string memory reason) { - revert(reason); - } catch { - revert("ERC1155: transfer to non ERC1155Receiver implementer"); - } - } - } - - function _doSafeBatchTransferAcceptanceCheck( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) - internal - { - if (to.isContract()) { - try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (bytes4 response) { - if (response != IERC1155Receiver(to).onERC1155BatchReceived.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); - } - } catch Error(string memory reason) { - revert(reason); - } catch { - revert("ERC1155: transfer to non ERC1155Receiver implementer"); - } - } - } - - function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { - uint256[] memory array = new uint256[](1); - array[0] = element; - - return array; - } -} diff --git a/assets/eip-3267/contracts/ERC1155/ERC1155TokenReceiver.sol b/assets/eip-3267/contracts/ERC1155/ERC1155TokenReceiver.sol deleted file mode 100644 index 9832abc..0000000 --- a/assets/eip-3267/contracts/ERC1155/ERC1155TokenReceiver.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; - -import "./IERC1155TokenReceiver.sol"; -import "@openzeppelin/contracts/introspection/ERC165.sol"; - -abstract contract ERC1155TokenReceiver is ERC165, IERC1155TokenReceiver { - constructor() { - _registerInterface( - ERC1155TokenReceiver(0).onERC1155Received.selector ^ - ERC1155TokenReceiver(0).onERC1155BatchReceived.selector - ); - } -} diff --git a/assets/eip-3267/contracts/ERC1155/ERC1155WithTotals.sol b/assets/eip-3267/contracts/ERC1155/ERC1155WithTotals.sol deleted file mode 100644 index 407356f..0000000 --- a/assets/eip-3267/contracts/ERC1155/ERC1155WithTotals.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import "@openzeppelin/contracts/math/SafeMath.sol"; -import { ERC1155 } from "./ERC1155.sol"; - -/// @title A base contract for an ERC-1155 contract with calculation of totals. -abstract contract ERC1155WithTotals is ERC1155 { - using SafeMath for uint256; - - // Mapping (token => total). - mapping(uint256 => uint256) private totalBalances; - - /// Construct a token contract with given description URI. - /// @param uri_ Description URI. - constructor (string memory uri_) ERC1155(uri_) { } - - // Overrides // - - // Need also update totals - commented out - // function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual override { - // return super._mintBatch(_originalAddress(to), ids, amounts, data); - // } - - // Need also update totals - commented out - // function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual override { - // return super._burnBatch(_originalAddress(account), ids, amounts); - // } - - /// Total supply of a token (conforms to `IERC1155Views`). - /// @param id Token ID. - /// @return Total supply. - function totalSupply(uint256 id) public view returns (uint256) { - return totalBalances[id]; - } - - /// Mint a token. - /// @param to Whom to mint to. - /// @param id Token ID. - /// @param value Amount to mint. - /// @param data Additional data. - function _mint(address to, uint256 id, uint256 value, bytes memory data) internal override { - require(to != address(0), "ERC1155: mint to the zero address"); - - _doMint(to, id, value); - emit TransferSingle(msg.sender, address(0), to, id, value); - - _doSafeTransferAcceptanceCheck(msg.sender, address(0), to, id, value, data); - } - - /// Mint zero or more tokens. - /// @param to Whom to mint to. - /// @param ids Token IDs. - /// @param values Amounts to mint. - /// @param data Additional data. - function _batchMint(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal { - require(to != address(0), "ERC1155: batch mint to the zero address"); - require(ids.length == values.length, "ERC1155: IDs and values must have same lengths"); - - for(uint i = 0; i < ids.length; i++) { - _doMint(to, ids[i], values[i]); - } - - emit TransferBatch(msg.sender, address(0), to, ids, values); - - _doSafeBatchTransferAcceptanceCheck(msg.sender, address(0), to, ids, values, data); - } - - /// Burn a token. - /// @param owner Whose tokens to burn. - /// @param id Token ID. - /// @param value Amount to mint. - function _burn(address owner, uint256 id, uint256 value) internal override { - _doBurn(owner, id, value); - emit TransferSingle(msg.sender, owner, address(0), id, value); - } - - /// Burn zero or more tokens. - /// @param owner Whose tokens to burn. - /// @param ids Token IDs. - /// @param values Amounts to mint. - function _batchBurn(address owner, uint256[] memory ids, uint256[] memory values) internal { - require(ids.length == values.length, "ERC1155: IDs and values must have same lengths"); - - for(uint i = 0; i < ids.length; i++) { - _doBurn(owner, ids[i], values[i]); - } - - emit TransferBatch(msg.sender, owner, address(0), ids, values); - } - - function _doMint(address to, uint256 id, uint256 value) private { - totalBalances[id] = totalBalances[id].add(value); - _balances[id][to] = _balances[id][to] + value; // The previous didn't overflow, therefore this doesn't overflow. - } - - function _doBurn(address from, uint256 id, uint256 value) private { - _balances[id][from] = _balances[id][from].sub(value); - totalBalances[id] = totalBalances[id] - value; // The previous didn't overflow, therefore this doesn't overflow. - } -} \ No newline at end of file diff --git a/assets/eip-3267/contracts/ERC1155/IERC1155.sol b/assets/eip-3267/contracts/ERC1155/IERC1155.sol deleted file mode 100644 index e763657..0000000 --- a/assets/eip-3267/contracts/ERC1155/IERC1155.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; - -import "@openzeppelin/contracts/introspection/IERC165.sol"; - -/** - @title ERC-1155 Multi Token Standard basic interface - @dev See https://eips.ethereum.org/EIPS/eip-1155 - */ -abstract contract IERC1155 is IERC165 { - event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); - - event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); - - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - - event URI(string value, uint256 indexed id); - - function balanceOf(address owner, uint256 id) public view virtual returns (uint256); - - function balanceOfBatch(address[] memory owners, uint256[] memory ids) public view virtual returns (uint256[] memory); - - function setApprovalForAll(address operator, bool approved) external virtual; - - function isApprovedForAll(address owner, address operator) external view virtual returns (bool); - - function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external virtual; - - function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external virtual; -} diff --git a/assets/eip-3267/contracts/ERC1155/IERC1155TokenReceiver.sol b/assets/eip-3267/contracts/ERC1155/IERC1155TokenReceiver.sol deleted file mode 100644 index 044260a..0000000 --- a/assets/eip-3267/contracts/ERC1155/IERC1155TokenReceiver.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; - -import "@openzeppelin/contracts/introspection/IERC165.sol"; - -/** - @title ERC-1155 Multi Token Receiver Interface - @dev See https://eips.ethereum.org/EIPS/eip-1155 -*/ -abstract contract IERC1155TokenReceiver is IERC165 { - - /** - @dev Handles the receipt of a single ERC1155 token type. This function is - called at the end of a `safeTransferFrom` after the balance has been updated. - To accept the transfer, this must return - `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` - (i.e. 0xf23a6e61, or its own function selector). - @param operator The address which initiated the transfer (i.e. msg.sender) - @param from The address which previously owned the token - @param id The ID of the token being transferred - @param value The amount of tokens being transferred - @param data Additional data with no specified format - @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed - */ - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) - external virtual - returns(bytes4); - - /** - @dev Handles the receipt of a multiple ERC1155 token types. This function - is called at the end of a `safeBatchTransferFrom` after the balances have - been updated. To accept the transfer(s), this must return - `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` - (i.e. 0xbc197c81, or its own function selector). - @param operator The address which initiated the batch transfer (i.e. msg.sender) - @param from The address which previously owned the token - @param ids An array containing ids of each token being transferred (order and length must match values array) - @param values An array containing amounts of each token being transferred (order and length must match ids array) - @param data Additional data with no specified format - @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed - */ - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) - external virtual - returns(bytes4); -} diff --git a/assets/eip-3267/contracts/ERC1155/README.md b/assets/eip-3267/contracts/ERC1155/README.md deleted file mode 100644 index 8f69c42..0000000 --- a/assets/eip-3267/contracts/ERC1155/README.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sections: - - title: Core - contracts: - - IERC1155 - - ERC1155 - - IERC1155TokenReceiver ---- - -This set of interfaces and contracts are all related to the [ERC1155 Multi Token Standard](https://eips.ethereum.org/EIPS/eip-1155). - -The EIP consists of two interfaces which fulfill different roles, found here as `IERC1155` and `IERC1155TokenReceiver`. Only `IERC1155` is required for a contract to be ERC1155 compliant. The basic functionality is implemented in `ERC1155`. diff --git a/assets/eip-3267/contracts/README.md b/assets/eip-3267/contracts/README.md deleted file mode 100644 index 47e04b2..0000000 --- a/assets/eip-3267/contracts/README.md +++ /dev/null @@ -1,31 +0,0 @@ -This directory contains contract sources for EIP 3267 draft. - -The audit of the contracts was paid for, now it is in progress. -(There are some FIXME/TODO comments for the auditors.) - -The contracts are to be compiled with Solidity 0.7.6. - -Dependencies (Node.js packages): - -- @openzeppelin/contracts 3.3.0 -- abdk-libraries-solidity 2.4.0 - -The contracts to be deployed are: -- SalaryWithDAO -- DefaultDAOInterface - -The sources: -- [ERC1155/ERC1155.sol](./ERC1155/ERC1155.sol) -- [ERC1155/ERC1155TokenReceiver.sol](./ERC1155/ERC1155TokenReceiver.sol) -- [ERC1155/ERC1155WithTotals.sol](./ERC1155/ERC1155WithTotals.sol) -- [ERC1155/IERC1155.sol](./ERC1155/IERC1155.sol) -- [ERC1155/IERC1155TokenReceiver.sol](./ERC1155/IERC1155TokenReceiver.sol) -- [BaseBidOnAddresses.sol](./BaseBidOnAddresses.sol) -- [BaseLock.sol](./BaseLock.sol) -- [BaseRestorableSalary.sol](./BaseRestorableSalary.sol) -- [BaseSalary.sol](./BaseSalary.sol) -- [BidOnAddresses.sol](./BidOnAddresses.sol) -- [DAOInterface.sol](./DAOInterface.sol) -- [DefaultDAOInterface.sol](./DefaultDAOInterface.sol) -- [Salary.sol](./Salary.sol) -- [SalaryWithDAO.sol](./SalaryWithDAO.sol) diff --git a/assets/eip-3267/contracts/Salary.sol b/assets/eip-3267/contracts/Salary.sol deleted file mode 100644 index 83d4808..0000000 --- a/assets/eip-3267/contracts/Salary.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import "./BaseSalary.sol"; - -/// @title "Salary" that is paid one token per second using minted conditionals. -/// @author Victor Porton -/// @notice Not audited, not enough tested. -contract Salary is BaseSalary { - /// @param _uri The ERC-1155 token URI. - constructor(string memory _uri) BaseSalary(_uri) { } - - /// Register a salary recipient. - /// - /// Can be called both before or after the oracle finish. However registering after the finish is useless. - /// - /// Anyone can register anyone (useful for robots registering a person). - /// - /// Registering another person is giving him money against his will (forcing to hire bodyguards, etc.), - /// but if one does not want, he can just not associate this address with his identity in his publications. - /// @param _customer The original address. - /// @param _oracleId The oracle ID. - /// @param _data The current data. - function registerCustomer(address _customer, uint64 _oracleId, bytes calldata _data) - virtual public returns (uint256) - { - return _registerCustomer(_customer, _oracleId, _data); - } -} \ No newline at end of file diff --git a/assets/eip-3267/contracts/SalaryWithDAO.sol b/assets/eip-3267/contracts/SalaryWithDAO.sol deleted file mode 100644 index cbe2110..0000000 --- a/assets/eip-3267/contracts/SalaryWithDAO.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.7.1; -import { ABDKMath64x64 } from "abdk-libraries-solidity/ABDKMath64x64.sol"; -import "./BaseRestorableSalary.sol"; -import "./DAOInterface.sol"; - -/// Salary system with a "DAO" that can assign attorneys to restore lost Ethereum accounts. -/// @author Victor Porton -/// @notice Not audited, not enough tested. -contract SalaryWithDAO is BaseRestorableSalary { - using ABDKMath64x64 for int128; - - /// The DAO interface. - DAOInterface public daoPlugin; - - /// When set to true, your account can't be moved to new address (by the DAO). - /// - /// By default new users are not under DAO control to avoid front-running of resigning control - /// by an evil DAO. - /// - /// Mapping (current address => under control) - mapping (address => bool) public underDAOControl; - - /// Mapping (current address => account has at least one salary). - mapping (address => bool) public accountHasSalary; - - // DAO share will be zero to prevent theft by voters and because it can be done instead by future voting. - // int128 public daoShare = int128(0).div(1); // zero by default - - /// Constructor. - /// @param _daoPlugin The DAO interface. - /// @param _uri The ERC-1155 token URI. - constructor(DAOInterface _daoPlugin, string memory _uri) - BaseRestorableSalary(_uri) - { - daoPlugin = _daoPlugin; - } - - /// Create an oracle for caclcualting salary amounts. - function createOracle() external returns (uint64) { - return _createOracle(); - } - - /// Register a salary recipient. - /// - /// Can be called both before or after the oracle finish. However registering after the finish is useless. - /// - /// Anyone can register anyone (useful for robots registering a person). - /// - /// Registering another person is giving him money against his will (forcing to hire bodyguards, etc.), - /// but if one does not want, he can just not associate this address with his identity in his publications. - /// @param _customer The original address. - /// @param _oracleId The oracle ID. - /// @param _underDAOControl If the registered address will be under DAO control. - /// @param _data The current data. - function registerCustomer(address _customer, uint64 _oracleId, bool _underDAOControl, bytes calldata _data) - virtual public returns (uint256) - { - address _orig = _originalAddress(_customer); - // Auditor: Check that this value is set to false, when (and if) necessary. - accountHasSalary[_customer] = true; - underDAOControl[_customer] = _underDAOControl; // We don't trigger and event to reduce gas usage. - return super._registerCustomer(_orig, _oracleId, _data); - } - - /// A user can agree for DAO control. Then his account can be restored by DAO for the expense - /// of the DAO assigned personnel or software being able to steal his funds. - /// - /// Be exteremely careful calling this method: If you refuse and lose your key, your funds are lost! - /// - /// Fishers may trick one to resign mistakenly. However, it's no much worse than just fishing for - /// withdrawing the salary token, because a user could just register anew and notify traders/oracles - /// that it's the same person. - function setDAOControl(bool _underControl) public { - address _orig = _originalAddress(msg.sender); - require(accountHasSalary[_orig], "Cannot resign account receiving a salary."); - underDAOControl[_orig] = _underControl; // We don't trigger and event to reduce gas usage. - } - - /// The DAO can replace itself. - function setDAO(DAOInterface _daoPlugin) public onlyDAO { - daoPlugin = _daoPlugin; - } - - /// Set the token URI. - function setURI(string memory _newuri) public onlyDAO { - _setURI(_newuri); - } - - // Overrides /// - - function checkAllowedRestoreAccount(address _oldAccount, address _newAccount) - public virtual override isUnderDAOControl(_oldAccount) - { - daoPlugin.checkAllowedRestoreAccount(_oldAccount, _newAccount); - } - - /// Allow the user to unrestore by himself? - /// We won't not allow it to `_oldAccount` because it may be a stolen private key. - /// We could allow it to `_newAccount`, but this makes no much sense, because - /// it would only prevent the user to do a theft by himself, let only DAO could be allowed to do. - function checkAllowedUnrestoreAccount(address _oldAccount, address _newAccount) - public virtual override isUnderDAOControl(_oldAccount) - { - daoPlugin.checkAllowedUnrestoreAccount(_oldAccount, _newAccount); - } - - // Internal // - - function _isDAO() internal view returns (bool) { - return msg.sender == address(daoPlugin); - } - - // Modifiers // - - modifier onlyDAO() { - require(_isDAO(), "Only DAO can do."); - _; - } - - /// @param _customer The current address. - modifier isUnderDAOControl(address _customer) { - require(underDAOControl[_customer], "Not under DAO control."); - _; - } -} diff --git a/assets/eip-3267/science-salaries.pdf b/assets/eip-3267/science-salaries.pdf deleted file mode 100644 index a6a243c..0000000 Binary files a/assets/eip-3267/science-salaries.pdf and /dev/null differ diff --git a/assets/eip-3448/MetaProxyFactory.sol b/assets/eip-3448/MetaProxyFactory.sol deleted file mode 100644 index e7c3679..0000000 --- a/assets/eip-3448/MetaProxyFactory.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.7.6; - -contract MetaProxyFactory { - /// @dev Creates a new proxy for `targetContract` with metadata from calldata. - /// Copies everything from calldata except the first 4 bytes. - /// @return addr A non-zero address if successful. - function _metaProxyFromCalldata (address targetContract) internal returns (address addr) { - // the following assembly code (init code + contract code) constructs a metaproxy. - assembly { - // load free memory pointer as per solidity convention - let start := mload(64) - // copy - let ptr := start - // deploy code (11 bytes) + first part of the proxy (21 bytes) - mstore(ptr, 0x600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73) - ptr := add(ptr, 32) - - // store the address of the contract to be called - mstore(ptr, shl(96, targetContract)) - // 20 bytes - ptr := add(ptr, 20) - - // the remaining proxy code... - mstore(ptr, 0x5af43d3d93803e603457fd5bf300000000000000000000000000000000000000) - // ...13 bytes - ptr := add(ptr, 13) - - // now calculdate the size and copy the metadata - // - 4 bytes function signature - let size := sub(calldatasize(), 4) - // copy - calldatacopy(ptr, 4, size) - ptr := add(ptr, size) - // store the size of the metadata at the end of the bytecode - mstore(ptr, size) - ptr := add(ptr, 32) - - // The size is deploy code + contract code + calldatasize - 4 + 32. - addr := create(0, start, sub(ptr, start)) - } - } - - /// @dev Creates a proxy for `targetContract` with metadata from `metadata`. - /// @return A non-zero address if successful. - function _metaProxyFromBytes (address targetContract, bytes memory metadata) internal returns (address) { - uint256 ptr; - assembly { - ptr := add(metadata, 32) - } - return _metaProxyFromMemory(targetContract, ptr, metadata.length); - } - - /// @dev Creates a new proxy for `targetContract` with metadata from memory starting at `offset` and `length` bytes. - /// @return addr A non-zero address if successful. - function _metaProxyFromMemory (address targetContract, uint256 offset, uint256 length) internal returns (address addr) { - // the following assembly code (init code + contract code) constructs a metaproxy. - assembly { - // load free memory pointer as per solidity convention - let start := mload(64) - // keep a copy - let ptr := start - // deploy code (11 bytes) + first part of the proxy (21 bytes) - mstore(ptr, 0x600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73) - ptr := add(ptr, 32) - - // store the address of the contract to be called - mstore(ptr, shl(96, targetContract)) - // 20 bytes - ptr := add(ptr, 20) - - // the remaining proxy code... - mstore(ptr, 0x5af43d3d93803e603457fd5bf300000000000000000000000000000000000000) - // ...13 bytes - ptr := add(ptr, 13) - - // copy the metadata - { - for { let i := 0 } lt(i, length) { i := add(i, 32) } { - mstore(add(ptr, i), mload(add(offset, i))) - } - } - ptr := add(ptr, length) - // store the size of the metadata at the end of the bytecode - mstore(ptr, length) - ptr := add(ptr, 32) - - // The size is deploy code + contract code + calldatasize - 4 + 32. - addr := create(0, start, sub(ptr, start)) - } - } -} diff --git a/assets/eip-3448/MetaProxyTest.sol b/assets/eip-3448/MetaProxyTest.sol deleted file mode 100644 index dd17554..0000000 --- a/assets/eip-3448/MetaProxyTest.sol +++ /dev/null @@ -1,190 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.7.6; - -import './MetaProxyFactory.sol'; - -/// @notice This contract includes test cases for the MetaProxy standard. -contract MetaProxyTest is MetaProxyFactory { - uint256 public someValue; - - event SomeEvent( - address a, - uint256 b, - uint256[] c - ); - event SomeData(bytes data); - - /// @notice One-time initializer. - function init () external payable { - require(someValue == 0); - - (, uint256 b, ) = MetaProxyTest(this).getMetadataViaCall(); - require(b > 0); - someValue = b; - } - - /// @notice MetaProxy construction via abi encoded bytes. - /// Arguments are reversed for testing purposes. - function createFromBytes ( - uint256[] calldata c, - uint256 b, - address a - ) external payable returns (address proxy) { - // creates a new proxy where the metadata is the result of abi.encode() - proxy = MetaProxyFactory._metaProxyFromBytes(address(this), abi.encode(a, b, c)); - require(proxy != address(0)); - // optional one-time setup, a constructor() substitute - MetaProxyTest(proxy).init{ value: msg.value }(); - } - - /// @notice MetaProxy construction via calldata. - function createFromCalldata ( - address a, - uint256 b, - uint256[] calldata c - ) external payable returns (address proxy) { - // creates a new proxy where the metadata is everything after the 4th byte from calldata. - proxy = MetaProxyFactory._metaProxyFromCalldata(address(this)); - require(proxy != address(0)); - // optional one-time setup, a constructor() substitute - MetaProxyTest(proxy).init{ value: msg.value }(); - } - - /// @notice Returns the metadata of this (MetaProxy) contract. - /// Only relevant with contracts created via the MetaProxy standard. - /// @dev This function is aimed to be invoked with- & without a call. - function getMetadataWithoutCall () public pure returns ( - address a, - uint256 b, - uint256[] memory c - ) { - bytes memory data; - assembly { - let posOfMetadataSize := sub(calldatasize(), 32) - let size := calldataload(posOfMetadataSize) - let dataPtr := sub(posOfMetadataSize, size) - data := mload(64) - // increment free memory pointer by metadata size + 32 bytes (length) - mstore(64, add(data, add(size, 32))) - mstore(data, size) - let memPtr := add(data, 32) - calldatacopy(memPtr, dataPtr, size) - } - return abi.decode(data, (address, uint256, uint256[])); - } - - /// @notice Returns the metadata of this (MetaProxy) contract. - /// Only relevant with contracts created via the MetaProxy standard. - /// @dev This function is aimed to to be invoked via a call. - function getMetadataViaCall () public pure returns ( - address a, - uint256 b, - uint256[] memory c - ) { - assembly { - let posOfMetadataSize := sub(calldatasize(), 32) - let size := calldataload(posOfMetadataSize) - let dataPtr := sub(posOfMetadataSize, size) - calldatacopy(0, dataPtr, size) - return(0, size) - } - } - - /// @notice Runs all test cases - function testAll () external payable { - (address a, uint256 b, uint256[] memory c) = abc(); - MetaProxyTest self = MetaProxyTest(address(this)); - - { - address proxy = self.createFromCalldata(a, b, c); - testProxy(proxy); - } - { - address proxy = self.createFromBytes(c, b, a); - testProxy(proxy); - } - } - - function abc () public returns (address a, uint256 b, uint256[] memory c) { - a = address(this); - b = 0xc0ffe; - c = new uint256[](9); - } - - function testProxy (address _proxy) public { - require(_proxy != address(0)); - - (address a, uint256 b, uint256[] memory c) = abc(); - MetaProxyTest proxy = MetaProxyTest(_proxy); - - { - (address x, uint256 y, uint256[] memory z) = proxy.getMetadataViaCall(); - require(a == x && b == y && keccak256(abi.encode(c)) == keccak256(abi.encode(z))); - } - { - (address x, uint256 y, uint256[] memory z) = proxy.getMetadataWithoutCall(); - require(a == x && b == y && keccak256(abi.encode(c)) == keccak256(abi.encode(z))); - } - - require(proxy.someValue() == b); - require(proxy.testReturnSingle() == b); - - bytes memory _bytes = hex'68656c6c6f20776f726c64'; - (uint256 x, uint256[] memory y) = proxy.testReturnMulti(_bytes, uint160(address(this)) + b); - require(x == b); - require(y.length == c.length); - - (bool success, bytes memory returnData) = _proxy.call(abi.encodeWithSignature('testRevert(string)', _bytes)); - require(success == false); - require(keccak256(returnData) == keccak256(abi.encodeWithSignature('Error(string)', _bytes))); - } - - function testReturnSingle () public returns (uint256) { - ( - address a, - uint256 b, - uint256[] memory c - ) = MetaProxyTest(this).getMetadataViaCall(); - - require(a == msg.sender); - require(b == someValue); - require(c.length == 9); - - emit SomeEvent(a, b, c); - - return b; - } - - function testReturnMulti (bytes memory data, uint256 xyz) public returns (uint256, uint256[] memory) { - ( - address a, - uint256 b, - uint256[] memory c - ) = getMetadataWithoutCall(); - - require(a == msg.sender); - require(b == someValue); - require(c.length == 9); - require(xyz == uint160(a) + b); - - bytes memory expected = hex'68656c6c6f20776f726c64'; - require(data.length == expected.length); - for (uint256 i = 0; i < expected.length; i++) { - require(data[i] == expected[i]); - } - - emit SomeEvent(a, b, c); - emit SomeData(data); - - return (b, c); - } - - function testRevert (string memory data) public { - (address a,,) = getMetadataWithoutCall(); - - // should evaluate to `true` - if (a != address(0)) { - revert(data); - } - } -} diff --git a/assets/eip-3450/lagrange.gif b/assets/eip-3450/lagrange.gif deleted file mode 100644 index de7268b..0000000 Binary files a/assets/eip-3450/lagrange.gif and /dev/null differ diff --git a/assets/eip-3450/wordlist.txt b/assets/eip-3450/wordlist.txt deleted file mode 100644 index 942040e..0000000 --- a/assets/eip-3450/wordlist.txt +++ /dev/null @@ -1,2048 +0,0 @@ -abandon -ability -able -about -above -absent -absorb -abstract -absurd -abuse -access -accident -account -accuse -achieve -acid -acoustic -acquire -across -act -action -actor -actress -actual -adapt -add -addict -address -adjust -admit -adult -advance -advice -aerobic -affair -afford -afraid -again -age -agent -agree -ahead -aim -air -airport -aisle -alarm -album -alcohol -alert -alien -all -alley -allow -almost -alone -alpha -already -also -alter -always -amateur -amazing -among -amount -amused -analyst -anchor -ancient -anger -angle -angry -animal -ankle -announce -annual -another -answer -antenna -antique -anxiety -any -apart -apology -appear -apple -approve -april -arch -arctic -area -arena -argue -arm -armed -armor -army -around -arrange -arrest -arrive -arrow -art -artefact -artist -artwork -ask -aspect -assault -asset -assist -assume -asthma -athlete -atom -attack -attend -attitude -attract -auction -audit -august -aunt -author -auto -autumn -average -avocado -avoid -awake -aware -away -awesome -awful -awkward -axis -baby -bachelor -bacon -badge -bag -balance -balcony -ball -bamboo -banana -banner -bar -barely -bargain -barrel -base -basic -basket -battle -beach -bean -beauty -because -become -beef -before -begin -behave -behind -believe -below -belt -bench -benefit -best -betray -better -between -beyond -bicycle -bid -bike -bind -biology -bird -birth -bitter -black -blade -blame -blanket -blast -bleak -bless -blind -blood -blossom -blouse -blue -blur -blush -board -boat -body -boil -bomb -bone -bonus -book -boost -border -boring -borrow -boss -bottom -bounce -box -boy -bracket -brain -brand -brass -brave -bread -breeze -brick -bridge -brief -bright -bring -brisk -broccoli -broken -bronze -broom -brother -brown -brush -bubble -buddy -budget -buffalo -build -bulb -bulk -bullet -bundle -bunker -burden -burger -burst -bus -business -busy -butter -buyer -buzz -cabbage -cabin -cable -cactus -cage -cake -call -calm -camera -camp -can -canal -cancel -candy -cannon -canoe -canvas -canyon -capable -capital -captain -car -carbon -card -cargo -carpet -carry -cart -case -cash -casino -castle -casual -cat -catalog -catch -category -cattle -caught -cause -caution -cave -ceiling -celery -cement -census -century -cereal -certain -chair -chalk -champion -change -chaos -chapter -charge -chase -chat -cheap -check -cheese -chef -cherry -chest -chicken -chief -child -chimney -choice -choose -chronic -chuckle -chunk -churn -cigar -cinnamon -circle -citizen -city -civil -claim -clap -clarify -claw -clay -clean -clerk -clever -click -client -cliff -climb -clinic -clip -clock -clog -close -cloth -cloud -clown -club -clump -cluster -clutch -coach -coast -coconut -code -coffee -coil -coin -collect -color -column -combine -come -comfort -comic -common -company -concert -conduct -confirm -congress -connect -consider -control -convince -cook -cool -copper -copy -coral -core -corn -correct -cost -cotton -couch -country -couple -course -cousin -cover -coyote -crack -cradle -craft -cram -crane -crash -crater -crawl -crazy -cream -credit -creek -crew -cricket -crime -crisp -critic -crop -cross -crouch -crowd -crucial -cruel -cruise -crumble -crunch -crush -cry -crystal -cube -culture -cup -cupboard -curious -current -curtain -curve -cushion -custom -cute -cycle -dad -damage -damp -dance -danger -daring -dash -daughter -dawn -day -deal -debate -debris -decade -december -decide -decline -decorate -decrease -deer -defense -define -defy -degree -delay -deliver -demand -demise -denial -dentist -deny -depart -depend -deposit -depth -deputy -derive -describe -desert -design -desk -despair -destroy -detail -detect -develop -device -devote -diagram -dial -diamond -diary -dice -diesel -diet -differ -digital -dignity -dilemma -dinner -dinosaur -direct -dirt -disagree -discover -disease -dish -dismiss -disorder -display -distance -divert -divide -divorce -dizzy -doctor -document -dog -doll -dolphin -domain -donate -donkey -donor -door -dose -double -dove -draft -dragon -drama -drastic -draw -dream -dress -drift -drill -drink -drip -drive -drop -drum -dry -duck -dumb -dune -during -dust -dutch -duty -dwarf -dynamic -eager -eagle -early -earn -earth -easily -east -easy -echo -ecology -economy -edge -edit -educate -effort -egg -eight -either -elbow -elder -electric -elegant -element -elephant -elevator -elite -else -embark -embody -embrace -emerge -emotion -employ -empower -empty -enable -enact -end -endless -endorse -enemy -energy -enforce -engage -engine -enhance -enjoy -enlist -enough -enrich -enroll -ensure -enter -entire -entry -envelope -episode -equal -equip -era -erase -erode -erosion -error -erupt -escape -essay -essence -estate -eternal -ethics -evidence -evil -evoke -evolve -exact -example -excess -exchange -excite -exclude -excuse -execute -exercise -exhaust -exhibit -exile -exist -exit -exotic -expand -expect -expire -explain -expose -express -extend -extra -eye -eyebrow -fabric -face -faculty -fade -faint -faith -fall -false -fame -family -famous -fan -fancy -fantasy -farm -fashion -fat -fatal -father -fatigue -fault -favorite -feature -february -federal -fee -feed -feel -female -fence -festival -fetch -fever -few -fiber -fiction -field -figure -file -film -filter -final -find -fine -finger -finish -fire -firm -first -fiscal -fish -fit -fitness -fix -flag -flame -flash -flat -flavor -flee -flight -flip -float -flock -floor -flower -fluid -flush -fly -foam -focus -fog -foil -fold -follow -food -foot -force -forest -forget -fork -fortune -forum -forward -fossil -foster -found -fox -fragile -frame -frequent -fresh -friend -fringe -frog -front -frost -frown -frozen -fruit -fuel -fun -funny -furnace -fury -future -gadget -gain -galaxy -gallery -game -gap -garage -garbage -garden -garlic -garment -gas -gasp -gate -gather -gauge -gaze -general -genius -genre -gentle -genuine -gesture -ghost -giant -gift -giggle -ginger -giraffe -girl -give -glad -glance -glare -glass -glide -glimpse -globe -gloom -glory -glove -glow -glue -goat -goddess -gold -good -goose -gorilla -gospel -gossip -govern -gown -grab -grace -grain -grant -grape -grass -gravity -great -green -grid -grief -grit -grocery -group -grow -grunt -guard -guess -guide -guilt -guitar -gun -gym -habit -hair -half -hammer -hamster -hand -happy -harbor -hard -harsh -harvest -hat -have -hawk -hazard -head -health -heart -heavy -hedgehog -height -hello -helmet -help -hen -hero -hidden -high -hill -hint -hip -hire -history -hobby -hockey -hold -hole -holiday -hollow -home -honey -hood -hope -horn -horror -horse -hospital -host -hotel -hour -hover -hub -huge -human -humble -humor -hundred -hungry -hunt -hurdle -hurry -hurt -husband -hybrid -ice -icon -idea -identify -idle -ignore -ill -illegal -illness -image -imitate -immense -immune -impact -impose -improve -impulse -inch -include -income -increase -index -indicate -indoor -industry -infant -inflict -inform -inhale -inherit -initial -inject -injury -inmate -inner -innocent -input -inquiry -insane -insect -inside -inspire -install -intact -interest -into -invest -invite -involve -iron -island -isolate -issue -item -ivory -jacket -jaguar -jar -jazz -jealous -jeans -jelly -jewel -job -join -joke -journey -joy -judge -juice -jump -jungle -junior -junk -just -kangaroo -keen -keep -ketchup -key -kick -kid -kidney -kind -kingdom -kiss -kit -kitchen -kite -kitten -kiwi -knee -knife -knock -know -lab -label -labor -ladder -lady -lake -lamp -language -laptop -large -later -latin -laugh -laundry -lava -law -lawn -lawsuit -layer -lazy -leader -leaf -learn -leave -lecture -left -leg -legal -legend -leisure -lemon -lend -length -lens -leopard -lesson -letter -level -liar -liberty -library -license -life -lift -light -like -limb -limit -link -lion -liquid -list -little -live -lizard -load -loan -lobster -local -lock -logic -lonely -long -loop -lottery -loud -lounge -love -loyal -lucky -luggage -lumber -lunar -lunch -luxury -lyrics -machine -mad -magic -magnet -maid -mail -main -major -make -mammal -man -manage -mandate -mango -mansion -manual -maple -marble -march -margin -marine -market -marriage -mask -mass -master -match -material -math -matrix -matter -maximum -maze -meadow -mean -measure -meat -mechanic -medal -media -melody -melt -member -memory -mention -menu -mercy -merge -merit -merry -mesh -message -metal -method -middle -midnight -milk -million -mimic -mind -minimum -minor -minute -miracle -mirror -misery -miss -mistake -mix -mixed -mixture -mobile -model -modify -mom -moment -monitor -monkey -monster -month -moon -moral -more -morning -mosquito -mother -motion -motor -mountain -mouse -move -movie -much -muffin -mule -multiply -muscle -museum -mushroom -music -must -mutual -myself -mystery -myth -naive -name -napkin -narrow -nasty -nation -nature -near -neck -need -negative -neglect -neither -nephew -nerve -nest -net -network -neutral -never -news -next -nice -night -noble -noise -nominee -noodle -normal -north -nose -notable -note -nothing -notice -novel -now -nuclear -number -nurse -nut -oak -obey -object -oblige -obscure -observe -obtain -obvious -occur -ocean -october -odor -off -offer -office -often -oil -okay -old -olive -olympic -omit -once -one -onion -online -only -open -opera -opinion -oppose -option -orange -orbit -orchard -order -ordinary -organ -orient -original -orphan -ostrich -other -outdoor -outer -output -outside -oval -oven -over -own -owner -oxygen -oyster -ozone -pact -paddle -page -pair -palace -palm -panda -panel -panic -panther -paper -parade -parent -park -parrot -party -pass -patch -path -patient -patrol -pattern -pause -pave -payment -peace -peanut -pear -peasant -pelican -pen -penalty -pencil -people -pepper -perfect -permit -person -pet -phone -photo -phrase -physical -piano -picnic -picture -piece -pig -pigeon -pill -pilot -pink -pioneer -pipe -pistol -pitch -pizza -place -planet -plastic -plate -play -please -pledge -pluck -plug -plunge -poem -poet -point -polar -pole -police -pond -pony -pool -popular -portion -position -possible -post -potato -pottery -poverty -powder -power -practice -praise -predict -prefer -prepare -present -pretty -prevent -price -pride -primary -print -priority -prison -private -prize -problem -process -produce -profit -program -project -promote -proof -property -prosper -protect -proud -provide -public -pudding -pull -pulp -pulse -pumpkin -punch -pupil -puppy -purchase -purity -purpose -purse -push -put -puzzle -pyramid -quality -quantum -quarter -question -quick -quit -quiz -quote -rabbit -raccoon -race -rack -radar -radio -rail -rain -raise -rally -ramp -ranch -random -range -rapid -rare -rate -rather -raven -raw -razor -ready -real -reason -rebel -rebuild -recall -receive -recipe -record -recycle -reduce -reflect -reform -refuse -region -regret -regular -reject -relax -release -relief -rely -remain -remember -remind -remove -render -renew -rent -reopen -repair -repeat -replace -report -require -rescue -resemble -resist -resource -response -result -retire -retreat -return -reunion -reveal -review -reward -rhythm -rib -ribbon -rice -rich -ride -ridge -rifle -right -rigid -ring -riot -ripple -risk -ritual -rival -river -road -roast -robot -robust -rocket -romance -roof -rookie -room -rose -rotate -rough -round -route -royal -rubber -rude -rug -rule -run -runway -rural -sad -saddle -sadness -safe -sail -salad -salmon -salon -salt -salute -same -sample -sand -satisfy -satoshi -sauce -sausage -save -say -scale -scan -scare -scatter -scene -scheme -school -science -scissors -scorpion -scout -scrap -screen -script -scrub -sea -search -season -seat -second -secret -section -security -seed -seek -segment -select -sell -seminar -senior -sense -sentence -series -service -session -settle -setup -seven -shadow -shaft -shallow -share -shed -shell -sheriff -shield -shift -shine -ship -shiver -shock -shoe -shoot -shop -short -shoulder -shove -shrimp -shrug -shuffle -shy -sibling -sick -side -siege -sight -sign -silent -silk -silly -silver -similar -simple -since -sing -siren -sister -situate -six -size -skate -sketch -ski -skill -skin -skirt -skull -slab -slam -sleep -slender -slice -slide -slight -slim -slogan -slot -slow -slush -small -smart -smile -smoke -smooth -snack -snake -snap -sniff -snow -soap -soccer -social -sock -soda -soft -solar -soldier -solid -solution -solve -someone -song -soon -sorry -sort -soul -sound -soup -source -south -space -spare -spatial -spawn -speak -special -speed -spell -spend -sphere -spice -spider -spike -spin -spirit -split -spoil -sponsor -spoon -sport -spot -spray -spread -spring -spy -square -squeeze -squirrel -stable -stadium -staff -stage -stairs -stamp -stand -start -state -stay -steak -steel -stem -step -stereo -stick -still -sting -stock -stomach -stone -stool -story -stove -strategy -street -strike -strong -struggle -student -stuff -stumble -style -subject -submit -subway -success -such -sudden -suffer -sugar -suggest -suit -summer -sun -sunny -sunset -super -supply -supreme -sure -surface -surge -surprise -surround -survey -suspect -sustain -swallow -swamp -swap -swarm -swear -sweet -swift -swim -swing -switch -sword -symbol -symptom -syrup -system -table -tackle -tag -tail -talent -talk -tank -tape -target -task -taste -tattoo -taxi -teach -team -tell -ten -tenant -tennis -tent -term -test -text -thank -that -theme -then -theory -there -they -thing -this -thought -three -thrive -throw -thumb -thunder -ticket -tide -tiger -tilt -timber -time -tiny -tip -tired -tissue -title -toast -tobacco -today -toddler -toe -together -toilet -token -tomato -tomorrow -tone -tongue -tonight -tool -tooth -top -topic -topple -torch -tornado -tortoise -toss -total -tourist -toward -tower -town -toy -track -trade -traffic -tragic -train -transfer -trap -trash -travel -tray -treat -tree -trend -trial -tribe -trick -trigger -trim -trip -trophy -trouble -truck -true -truly -trumpet -trust -truth -try -tube -tuition -tumble -tuna -tunnel -turkey -turn -turtle -twelve -twenty -twice -twin -twist -two -type -typical -ugly -umbrella -unable -unaware -uncle -uncover -under -undo -unfair -unfold -unhappy -uniform -unique -unit -universe -unknown -unlock -until -unusual -unveil -update -upgrade -uphold -upon -upper -upset -urban -urge -usage -use -used -useful -useless -usual -utility -vacant -vacuum -vague -valid -valley -valve -van -vanish -vapor -various -vast -vault -vehicle -velvet -vendor -venture -venue -verb -verify -version -very -vessel -veteran -viable -vibrant -vicious -victory -video -view -village -vintage -violin -virtual -virus -visa -visit -visual -vital -vivid -vocal -voice -void -volcano -volume -vote -voyage -wage -wagon -wait -walk -wall -walnut -want -warfare -warm -warrior -wash -wasp -waste -water -wave -way -wealth -weapon -wear -weasel -weather -web -wedding -weekend -weird -welcome -west -wet -whale -what -wheat -wheel -when -where -whip -whisper -wide -width -wife -wild -will -win -window -wine -wing -wink -winner -winter -wire -wisdom -wise -wish -witness -wolf -woman -wonder -wood -wool -word -work -world -worry -worth -wrap -wreck -wrestle -wrist -write -wrong -yard -year -yellow -you -young -youth -zebra -zero -zone -zoo diff --git a/assets/eip-3475/ERC3475.sol b/assets/eip-3475/ERC3475.sol deleted file mode 100644 index 43448e1..0000000 --- a/assets/eip-3475/ERC3475.sol +++ /dev/null @@ -1,433 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./interfaces/IERC3475.sol"; - -contract ERC3475 is IERC3475 { - /** - * @notice this Struct is representing the Nonce properties as an object - * - */ - struct Nonce { - // stores the values corresponding to the dates (issuance and maturity date). - mapping(uint256 => IERC3475.Values) _values; - // storing the issuance of the issued bonds with their balances. - mapping(address => uint256) _balances; - // defines the mapping for amount of bonds that can be delegated by Owner => operator address. - mapping(address => mapping(address => uint256)) _allowances; - - // Overall supplies of this nonce - uint256 _activeSupply; - uint256 _burnedSupply; - uint256 _redeemedSupply; - } - - /** - * @notice this Struct is representing the Class properties as an object - * and can be retrieved by the classId - */ - struct Class { - mapping(uint256 => IERC3475.Values) _values; - mapping(uint256 => IERC3475.Metadata) _nonceMetadata; - mapping(uint256 => Nonce) nonces; - } - - mapping(address => mapping(address => bool)) operatorApprovals; - - // from classId given - mapping(uint256 => Class) internal _classes; - mapping(uint256 => IERC3475.Metadata) _classMetadata; - - /** - * @notice Here the constructor is just to initialize a class and nonce, - * in practice, you will have a function to create a new class and nonce - * to be deployed during the initial deployment cycle - */ - constructor() { - // define "symbol of the given class"; - _classMetadata[0].title = "symbol"; - _classMetadata[0]._type = "string"; - _classMetadata[0].description = "symbol of the class"; - // define "period of the class"; - _classMetadata[5].title = "period"; - _classMetadata[5]._type = "int"; - _classMetadata[5].description = "value (in months) about maturity time"; - - - - - // describing the symbol of the different class - _classes[0]._values[0].stringValue = "DBIT Fix 6M"; - _classes[1]._values[0].stringValue = "DBIT Fix test Instantaneous"; - - - // define the maturity time period (for the test class). - _classes[0]._values[5].uintValue = 10; - _classes[1]._values[5].uintValue = 1; - - // write the time of maturity to nonce values, in other implementation, a create nonce function can be added - _classes[0].nonces[0]._values[0].uintValue = block.timestamp + 180 days; - _classes[0].nonces[1]._values[0].uintValue = block.timestamp + 181 days; - _classes[0].nonces[2]._values[0].uintValue = block.timestamp + 182 days; - - // test for review the instantaneous class - _classes[1].nonces[0]._values[0].uintValue = block.timestamp + 1; - _classes[1].nonces[1]._values[0].uintValue = block.timestamp + 2; - _classes[1].nonces[2]._values[0].uintValue = block.timestamp + 3; - - // define metadata explaining "maturity of the nonce"; - _classes[0]._nonceMetadata[0].title = "maturity"; - _classes[0]._nonceMetadata[0]._type = "int"; - _classes[0]._nonceMetadata[0].description = "maturity date in integer"; - - _classes[1]._nonceMetadata[0].title = "maturity"; - _classes[1]._nonceMetadata[0]._type = "int"; - _classes[1]._nonceMetadata[0].description = "maturity date in integer"; - - // initializing all of the nonces for issued bonds - _classes[0].nonces[0]._values[0].boolValue = true; - _classes[0].nonces[1]._values[0].boolValue = true; - _classes[0].nonces[2]._values[0].boolValue = true; - } - - // Writable functions. - function transferFrom( - address _from, - address _to, - Transaction[] calldata _transactions - ) public virtual override { - require( - _from != address(0), - "ERC3475: can't transfer from the zero address" - ); - require( - _to != address(0), - "ERC3475:use burn() instead" - ); - require( - msg.sender == _from || - isApprovedFor(_from, msg.sender), - "ERC3475:caller-not-owner-or-approved" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - _transferFrom(_from, _to, _transactions[i]); - } - emit Transfer(msg.sender, _from, _to, _transactions); - } - - function transferAllowanceFrom( - address _from, - address _to, - Transaction[] calldata _transactions - ) public virtual override { - require( - _from != address(0), - "ERC3475: can't transfer allowed amt from zero address" - ); - require( - _to != address(0), - "ERC3475: use burn() instead" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - require( - _transactions[i]._amount <= allowance(_from, msg.sender, _transactions[i].classId, _transactions[i].nonceId), - "ERC3475:caller-not-owner-or-approved" - ); - _transferAllowanceFrom(msg.sender, _from, _to, _transactions[i]); - } - emit Transfer(msg.sender, _from, _to, _transactions); - } - - function issue(address _to, Transaction[] calldata _transactions) - external - virtual - override - { - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - require( - _to != address(0), - "ERC3475: can't issue to the zero address" - ); - _issue(_to, _transactions[i]); - } - emit Issue(msg.sender, _to, _transactions); - } - - function redeem(address _from, Transaction[] calldata _transactions) - external - virtual - override - { - require( - _from != address(0), - "ERC3475: can't redeem from the zero address" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - (, uint256 progressRemaining) = getProgress( - _transactions[i].classId, - _transactions[i].nonceId - ); - require( - progressRemaining == 0, - "ERC3475 Error: Not redeemable" - ); - _redeem(_from, _transactions[i]); - } - emit Redeem(msg.sender, _from, _transactions); - } - - function burn(address _from, Transaction[] calldata _transactions) - external - virtual - override - { - require( - _from != address(0), - "ERC3475: can't burn from the zero address" - ); - require( - msg.sender == _from || - isApprovedFor(_from, msg.sender), - "ERC3475: caller-not-owner-or-approved" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - _burn(_from, _transactions[i]); - } - emit Burn(msg.sender, _from, _transactions); - } - - function approve(address _spender, Transaction[] calldata _transactions) - external - virtual - override - { - for (uint256 i = 0; i < _transactions.length; i++) { - _classes[_transactions[i].classId] - .nonces[_transactions[i].nonceId] - ._allowances[msg.sender][_spender] = _transactions[i]._amount; - } - } - - function setApprovalFor( - address operator, - bool approved - ) public virtual override { - operatorApprovals[msg.sender][operator] = approved; - emit ApprovalFor(msg.sender, operator, approved); - } - - // READABLES - function totalSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return (activeSupply(classId, nonceId) + - burnedSupply(classId, nonceId) + - redeemedSupply(classId, nonceId) - ); - } - - function activeSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return _classes[classId].nonces[nonceId]._activeSupply; - } - - function burnedSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return _classes[classId].nonces[nonceId]._burnedSupply; - } - - function redeemedSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return _classes[classId].nonces[nonceId]._redeemedSupply; - } - - function balanceOf( - address account, - uint256 classId, - uint256 nonceId - ) public view override returns (uint256) { - require( - account != address(0), - "ERC3475: balance query for the zero address" - ); - return _classes[classId].nonces[nonceId]._balances[account]; - } - - function classMetadata(uint256 metadataId) - external - view - override - returns (Metadata memory) { - return (_classMetadata[metadataId]); - } - - function nonceMetadata(uint256 classId, uint256 metadataId) - external - view - override - returns (Metadata memory) { - return (_classes[classId]._nonceMetadata[metadataId]); - } - - function classValues(uint256 classId, uint256 metadataId) - external - view - override - returns (Values memory) { - return (_classes[classId]._values[metadataId]); - } - - function nonceValues(uint256 classId, uint256 nonceId, uint256 metadataId) - external - view - override - returns (Values memory) { - return (_classes[classId].nonces[nonceId]._values[metadataId]); - } - - /** determines the progress till the redemption of the bonds is valid (based on the type of bonds class). - * @notice ProgressAchieved and `progressRemaining` is abstract. - For e.g. we are giving time passed and time remaining. - */ - function getProgress(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256 progressAchieved, uint256 progressRemaining){ - uint256 issuanceDate = _classes[classId].nonces[nonceId]._values[0].uintValue; - uint256 maturityDate = issuanceDate + _classes[classId].nonces[nonceId]._values[5].uintValue; - - // check whether the bond is being already initialized: - progressAchieved = block.timestamp - issuanceDate; - progressRemaining = block.timestamp < maturityDate - ? maturityDate - block.timestamp - : 0; - } - /** - gets the allowance of the bonds identified by (classId,nonceId) held by _owner to be spend by spender. - */ - function allowance( - address _owner, - address spender, - uint256 classId, - uint256 nonceId - ) public view virtual override returns (uint256) { - return _classes[classId].nonces[nonceId]._allowances[_owner][spender]; - } - - /** - checks the status of approval to transfer the ownership of bonds by _owner to operator. - */ - function isApprovedFor( - address _owner, - address operator - ) public view virtual override returns (bool) { - return operatorApprovals[_owner][operator]; - } - - // INTERNALS - function _transferFrom( - address _from, - address _to, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not enough bond to transfer" - ); - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._balances[_to] += _transaction._amount; - } - - function _transferAllowanceFrom( - address _operator, - address _from, - address _to, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not allowed amount" - ); - // reducing the allowance and decreasing accordingly. - nonce._allowances[_from][_operator] -= _transaction._amount; - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._balances[_to] += _transaction._amount; - } - - function _issue( - address _to, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - - //transfer balance - nonce._balances[_to] += _transaction._amount; - nonce._activeSupply += _transaction._amount; - } - - - function _redeem( - address _from, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - // verify whether _amount of bonds to be redeemed are sufficient available for the given nonce of the bonds - - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not enough bond to transfer" - ); - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._activeSupply -= _transaction._amount; - nonce._redeemedSupply += _transaction._amount; - } - - - function _burn( - address _from, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - // verify whether _amount of bonds to be burned are sfficient available for the given nonce of the bonds - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not enough bond to transfer" - ); - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._activeSupply -= _transaction._amount; - nonce._burnedSupply += _transaction._amount; - } - -} diff --git a/assets/eip-3475/ERC3475.test.ts b/assets/eip-3475/ERC3475.test.ts deleted file mode 100644 index cf94602..0000000 --- a/assets/eip-3475/ERC3475.test.ts +++ /dev/null @@ -1,333 +0,0 @@ -// @notice : run the typechain generate commance into the smrt contract repository (truffle in our case), after the contracts are compiled. -import { ERC3475Instance } from "../types/truffle-contracts"; - -function sleep(ms: any) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - -} - - - - -const Bond = artifacts.require("ERC3475"); - -contract('Bond', async (accounts: string[]) => { - - let bondContract: ERC3475Instance; - const lender = accounts[1]; - const operator = accounts[2]; - const secondaryMarketBuyer = accounts[3]; - const secondaryMarketBuyer2 = accounts[4]; - const spender = accounts[5]; - - const DBITClassId: number = 0; - const firstNonceId: number = 0; - - interface _transaction { - classId: string | number | BN; - nonceId: string | number | BN; - amount: string | number | BN; - } - - before('testing', async () => { - bondContract = await Bond.deployed(); - - }) - - it('should issue bonds to a lender', async () => { - let _transactionIssuer: _transaction[] - = - [{ - classId: DBITClassId, - nonceId: firstNonceId, - amount: 7000 - }]; - - await bondContract.issue(lender, _transactionIssuer, { from: accounts[0] }) - await bondContract.issue(lender, _transactionIssuer, { from: accounts[0] }) - const balance = (await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber() - const activeSupply = (await bondContract.activeSupply(DBITClassId, firstNonceId)).toNumber() - assert.equal(balance, 14000); - assert.equal(activeSupply, 14000); - }) - it('lender should be able to transfer bonds to another address', async () => { - - const transferBonds: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 2000 - }]; - await bondContract.transferFrom(lender, secondaryMarketBuyer, transferBonds, { from: lender }) - - const lenderBalance = (await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber() - const secondaryBuyerBalance = (await bondContract.balanceOf(secondaryMarketBuyer, DBITClassId, firstNonceId)).toNumber() - const activeSupply = (await bondContract.activeSupply(DBITClassId, firstNonceId)).toNumber() - - assert.equal(lenderBalance, 12000); - assert.equal(secondaryBuyerBalance, 2000); - assert.equal(activeSupply, 14000); - }) - it('operator should be able to manipulate bonds after approval', async () => { - const transactionApproval: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 2000 - }]; - - await bondContract.setApprovalFor(operator, true, { from: lender }) - const isApproved = await bondContract.isApprovedFor(lender, operator); - assert.isTrue(isApproved); - await bondContract.transferFrom(lender, secondaryMarketBuyer2, transactionApproval, { from: operator }) - expect((await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber()).to.equal(10000); - expect((await bondContract.balanceOf(secondaryMarketBuyer2, DBITClassId, firstNonceId)).toNumber()).to.equal(2000); - - }) - - it('lender should redeem bonds when conditions are met', async () => { - const redemptionTransaction: _transaction[] = [ - { - classId: 1, - nonceId: 1, - amount: 2000 - - }, - ]; - await bondContract.issue(accounts[2],redemptionTransaction, {from: accounts[2]}); - assert.equal((await bondContract.balanceOf(accounts[2], 1, 1)).toNumber(), 2000); - // adding delay for passing the redemption time period. - await sleep(7000); - - await bondContract.redeem(accounts[2], redemptionTransaction, {from:accounts[2]}); - - assert.equal((await bondContract.balanceOf(accounts[2], DBITClassId, firstNonceId)).toNumber(), 0); - }) - - - it('lender should not be able to redeem bonds when conditions are not met', async () => { - const redemptionTransaction: _transaction[] = [ - - { - classId: 0, - nonceId: 0, - amount: 2000 - - }, - - ]; - - await bondContract.issue(accounts[2],redemptionTransaction, {from: accounts[2]}); - assert.equal((await bondContract.balanceOf(accounts[2], 0, 0)).toNumber(), 2000); - try { - await bondContract.redeem(accounts[2], redemptionTransaction, {from:accounts[2]}); - } - catch(e:any){ - assert.isTrue(true); - } - - }) - //////////////////// UNIT TESTS ////////////////////////////// - - it('should transfer bonds from an caller address to another', async () => { - const transactionTransfer: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }]; - await bondContract.issue(lender, transactionTransfer, { from: lender }); - const tx = (await bondContract.transferFrom(lender, secondaryMarketBuyer, transactionTransfer, {from:lender})).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should issue bonds to a given address', async () => { - - const transactionIssue: _transaction[] = [ - { - classId: 1, - nonceId: firstNonceId, - amount: 500 - - } - ]; - const tx = (await bondContract.issue(lender, transactionIssue)).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should redeem bonds from a given address', async () => { - const transactionRedeem: _transaction[] = [ - { - classId: 1, - nonceId: firstNonceId, - amount: 500 - - }]; - await bondContract.issue(lender, transactionRedeem, {from: lender}); - sleep(7000); - - const tx = (await bondContract.redeem(lender, transactionRedeem, {from:lender})).tx; - - console.log(tx) - assert.isString(tx); - }) - - it('should burn bonds from a given address', async () => { - const transactionRedeem: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }]; - - await bondContract.issue(lender, transactionRedeem, {from: lender}); - const tx = (await bondContract.burn(lender, transactionRedeem, {from:lender})).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should approve spender to manage a given amount of bonds from the caller address', async () => { - const transactionApprove: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }]; - - await bondContract.issue(lender, transactionApprove, {from: lender}); - const tx = (await bondContract.approve(spender, transactionApprove)).tx; - console.log(tx) - assert.isString(tx); - }) - - it('setApprovalFor (called by bond owner) should be able to give operator permissions to manage bonds for given classId', async () => { - const tx = (await bondContract.setApprovalFor(operator, true, { from: lender })).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should batch approve', async () => { - - const transactionApprove: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }, - { classId: 1, nonceId: 0, amount: 900 } - - ]; - - - await await bondContract.issue(spender,transactionApprove, {from:spender}); - const tx = (await bondContract.approve(spender, transactionApprove, {from:spender})).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should return the active supply', async () => { - const activeSupply = (await bondContract.activeSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(activeSupply) - assert.isNumber(activeSupply); - }) - - it('should return the redeemed supply', async () => { - const redeemedSupply = (await bondContract.redeemedSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(redeemedSupply) - assert.isNumber(redeemedSupply); - }) - - it('should return the burned supply', async () => { - const burnedSupply = (await bondContract.burnedSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(burnedSupply) - assert.isNumber(burnedSupply); - }) - - it('should return the total supply', async () => { - const totalSupply = (await bondContract.totalSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(totalSupply) - assert.isNumber(totalSupply); - }) - - it('should return the balanceOf a bond of a given address', async () => { - const balanceOf = (await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber(); - console.log(balanceOf) - assert.isNumber(balanceOf); - }) - - it('should return the symbol of a class of bond', async () => { - let metadataId = 0; - const symbol: { - stringValue: string; - uintValue: BN; - addressValue: string; - boolValue: boolean; - } = (await bondContract.classValues(DBITClassId, metadataId)); - console.log(JSON.stringify(symbol)); - assert.isString(symbol.stringValue); - }) - - it('should return the Values for given bond class', async () => { - const metadataId = 0; - - let _transactionIssuer: _transaction[] - = - [{ - classId: DBITClassId, - nonceId: firstNonceId, - amount: 7000 - }]; - - await bondContract.issue(lender, _transactionIssuer, { from: accounts[0] }) - const valuesClass = (await bondContract.classValues(DBITClassId, metadataId)); - console.log("class infos: ", JSON.stringify(valuesClass)); - assert.isString(valuesClass.toString()); - }) - - it('should return the infos of a nonce for given bond class', async () => { - const metadataId = 0; - const infos = (await bondContract.nonceValues(DBITClassId, firstNonceId, metadataId)); - console.log("nonce infos: ", JSON.stringify(infos)) - assert.isString(infos.toString()); - }) - - it('should return if an operator is approved on a class and nonce given for an address', async () => { - const isApproved = (await bondContract.isApprovedFor(lender, operator)); - console.log("operator is Approved? : ", isApproved) - assert.isBoolean(isApproved); - }) - - it('should return if its redeemable', async () => { - let _transactionIssuer: _transaction[] - = - [{ - classId: 1, - nonceId: 1, - amount: 7000 - }]; - - await bondContract.issue(accounts[1], _transactionIssuer, { from: accounts[1] }) - const getProgress = await bondContract.getProgress(1,1); - console.log("is Redeemable? : ", getProgress[1].toNumber() >= 0) - assert.isNumber(getProgress[1].toNumber()); - - }) - - it('should set allowance of a spender', async () => { - - const allowance = (await bondContract.allowance(lender, spender, DBITClassId, firstNonceId, {from:lender})).toNumber(); - console.log("allowance : ", allowance) - assert.isNumber(allowance); - }) - - it('should return if operator is approved for', async () => { - const isApproved = (await bondContract.isApprovedFor(lender, operator)); - console.log("operator is Approved? : ", isApproved) - assert.isTrue(isApproved); - }) - -}); diff --git a/assets/eip-3475/Metadata.md b/assets/eip-3475/Metadata.md deleted file mode 100644 index ac7c36f..0000000 --- a/assets/eip-3475/Metadata.md +++ /dev/null @@ -1,92 +0,0 @@ -# Metadata standards - - -This documentation consists of various JSON schemas (examples or standards) that can be referenced by the reader of this EIP for implementing EIP-3475 bonds storage. - -## 1. Description metadata: - -```json -[ - { - "title": "defining the title information", - "_type": "explaining the type of the title information added", - "description": "little description about the information stored in the bond", - } -] -``` - -Example: adding details in bonds describing the local jurisdiction of the bonds where it's issued: - -```json -{ -"title": "localisation", -"_type": "string", -"description": "jurisdiction law codes compatibility" -"values": ["fr ", "de", "ch"] -} -``` -The 'values' field defined above can also be ISO codes or other hex standard representation. -## 2. Nonce metadata: - -- **Information defining the state of the bond** - -```json -[ - { - "title": "maturity", - "_type": "uint", - "description": "Lorem ipsum...", - "values": [0, 0, 0] - } -] -``` - - -## 3. Class metadata: - -```json -[ - { - "title": "symbol", - "_type": "string", - "description": "Lorem ipsum...", - "values": ["Class symbol 1", "Class symbol 2", "Class symbol 3"], - }, - { - "title": "issuer", - "_type": "string", - "description": "Lorem ipsum...", - "values": ["Issuer name 1", "Issuer name 2", "Issuer name 3"], - }, - - { - "title": "issuer_address", - "_type": "address", - "description": "Lorem ipsum...", - "values":["Address 1.", "Address 2", "Address 3"] - }, - - { - "title": "class_type", - "_type": "string", - "description": "Lorem ipsum...", - "values": ["Class Type 1", "Class Type 2", "Class Type 3"] - }, - - { - "title": "token_address", - "_type": "address", - "description": "Lorem ipsum...", - "values":["Address 1.", "Address 2", "Address 3"] - }, - - { - "title": "period", - "_type": "uint", - "description": "Lorem ipsum...", - "values": [0, 0, 0] - } -] -``` -## Examples of other standards: - - ISO-20022 standard is the recently adopted standard by banks for communicating financial operators (Banks, trading intermediaries, underwriters) that also include bond operations. diff --git a/assets/eip-3475/interfaces/IERC3475.sol b/assets/eip-3475/interfaces/IERC3475.sol deleted file mode 100644 index 9fa2b1b..0000000 --- a/assets/eip-3475/interfaces/IERC3475.sol +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - - -pragma solidity ^0.8.0; - - -interface IERC3475 { - // STRUCTURE - /** - * @dev Values structure of the Metadata - */ - struct Values { - string stringValue; - uint uintValue; - address addressValue; - bool boolValue; - } - /** - * @dev structure allows to define particular bond metadata (ie the values in the class as well as nonce inputs). - * @notice 'title' defining the title information, - * @notice '_type' explaining the data type of the title information added (eg int, bool, address), - * @notice 'description' explains little description about the information stored in the bond", - */ - struct Metadata { - string title; - string _type; - string description; - } - /** - * @dev structure that defines the parameters for specific issuance of bonds and amount which are to be transferred/issued/given allowance, etc. - * @notice this structure is used to streamline the input parameters for functions of this standard with that of other Token standards like ERC20. - * @classId is the class id of the bond. - * @nonceId is the nonce id of the given bond class. This param is for distinctions of the issuing conditions of the bond. - * @amount is the amount of the bond that will be transferred. - */ - struct Transaction { - uint256 classId; - uint256 nonceId; - uint256 _amount; - } - - // WRITABLES - /** - * @dev allows the transfer of a bond from one address to another (either single or in batches). - * @param _from is the address of the holder whose balance is about to decrease. - * @param _to is the address of the recipient whose balance is about to increase. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be transferred}. - */ - function transferFrom(address _from, address _to, Transaction[] calldata _transactions) external; - /** - * @dev allows the transfer of allowance from one address to another (either single or in batches). - * @param _from is the address of the holder whose balance about to decrease. - * @param _to is the address of the recipient whose balance is about to increased. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be allowed to transferred}. - */ - function transferAllowanceFrom(address _from, address _to, Transaction[] calldata _transactions) external; - /** - * @dev allows issuing of any number of bond types to an address(either single/batched issuance). - * The calling of this function needs to be restricted to bond issuer contract. - * @param _to is the address to which the bond will be issued. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be issued for given whitelisted bond}. - */ - function issue(address _to, Transaction[] calldata _transactions) external; - /** - * @dev allows redemption of any number of bond types from an address. - * The calling of this function needs to be restricted to bond issuer contract. - * @param _from is the address _from which the bond will be redeemed. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be redeemed for given whitelisted bond}. - */ - function redeem(address _from, Transaction[] calldata _transactions) external; - - /** - * @dev allows the transfer of any number of bond types from an address to another. - * The calling of this function needs to be restricted to bond issuer contract. - * @param _from is the address of the holder whose balance about to decrees. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be redeemed for given whitelisted bond}. - */ - function burn(address _from, Transaction[] calldata _transactions) external; - - /** - * @dev Allows _spender to withdraw from your account multiple times, up to the amount. - * @notice If this function is called again, it overwrites the current allowance with amount. - * @param _spender is the address the caller approve for his bonds. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be approved for given whitelisted bond}. - */ - function approve(address _spender, Transaction[] calldata _transactions) external; - - /** - * @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. - * @dev MUST emit the ApprovalForAll event on success. - * @param _operator Address to add to the set of authorized operators - * @param _approved "True" if the operator is approved, "False" to revoke approval. - */ - function setApprovalFor(address _operator, bool _approved) external; - - // READABLES - - /** - * @dev Returns the total supply of the bond in question. - */ - function totalSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the redeemed supply of the bond in question. - */ - function redeemedSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the active supply of the bond in question. - */ - function activeSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the burned supply of the bond in question. - */ - function burnedSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the balance of the giving bond classId and bond nonce. - */ - function balanceOf(address _account, uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the JSON metadata of the classes. - * The metadata SHOULD follow a set of structure explained later in eip-3475.md - * @param metadataId is the index corresponding to the class parameter that you want to return from mapping. - */ - function classMetadata(uint256 metadataId) external view returns ( Metadata memory); - - /** - * @dev Returns the JSON metadata of the Values of the nonces in the corresponding class. - * @param classId is the specific classId of which you want to find the metadata of the corresponding nonce. - * @param metadataId is the index corresponding to the class parameter that you want to return from mapping. - * @notice The metadata SHOULD follow a set of structure explained later in metadata section. - */ - function nonceMetadata(uint256 classId, uint256 metadataId) external view returns ( Metadata memory); - - /** - * @dev Returns the values of the given classId. - * @param classId is the specific classId of which we want to return the parameter. - * @param metadataId is the index corresponding to the class parameter that you want to return from mapping. - * the metadata SHOULD follow a set of structures explained in eip-3475.md - */ - function classValues(uint256 classId, uint256 metadataId) external view returns ( Values memory); - - /** - * @dev Returns the values of given nonceId. - * @param metadataId index number of structure as explained in the metadata section in EIP-3475. - * @param classId is the class of bonds for which you determine the nonce. - * @param nonceId is the nonce for which you return the value struct info. - * Returns the values object corresponding to the given value. - */ - function nonceValues(uint256 classId, uint256 nonceId, uint256 metadataId) external view returns ( Values memory); - - /** - * @dev Returns the information about the progress needed to redeem the bond identified by classId and nonceId. - * @notice Every bond contract can have its own logic concerning the progress definition. - * @param classId The class of bonds. - * @param nonceId is the nonce of bonds for finding the progress. - * Returns progressAchieved is the current progress achieved. - * Returns progressRemaining is the remaining progress. - */ - function getProgress(uint256 classId, uint256 nonceId) external view returns (uint256 progressAchieved, uint256 progressRemaining); - - /** - * @notice Returns the amount that spender is still allowed to withdraw from _owner (for given classId and nonceId issuance) - * @param _owner is the address whose owner allocates some amount to the _spender address. - * @param classId is the classId of the bond. - * @param nonceId is the nonce corresponding to the class for which you are approving the spending of total amount of bonds. - */ - function allowance(address _owner, address _spender, uint256 classId, uint256 nonceId) external view returns (uint256); - /** - * @notice Queries the approval status of an operator for bonds (for all classes and nonce issuances of owner). - * @param _owner is the current holder of the bonds for all classes/nonces. - * @param _operator is the address with access to the bonds of _owner for transferring. - * Returns "true" if the operator is approved, "false" if not. - */ - function isApprovedFor(address _owner, address _operator) external view returns (bool); - - // EVENTS - /** - * @notice MUST trigger when tokens are transferred, including zero value transfers. - * e.g: - emit Transfer(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, 0x492Af743654549b12b1B807a9E0e8F397E44236E,0x3d03B6C79B75eE7aB35298878D05fe36DC1fEf, [IERC3475.Transaction(1,14,500)]) - means that operator 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef wants to transfer 500 bonds of class 1 , Nonce 14 of owner 0x492Af743654549b12b1B807a9E0e8F397E44236E to address 0x3d03B6C79B75eE7aB35298878D05fe36DC1fEf. - */ - event Transfer(address indexed _operator, address indexed _from, address indexed _to, Transaction[] _transactions); - /** - * @notice MUST trigger when tokens are issued - * @notice Issue MUST trigger when Bonds are issued. This SHOULD not include zero value Issuing. - * @dev This SHOULD not include zero value issuing. - * @dev Issue MUST be triggered when the operator (i.e Bank address) contract issues bonds to the given entity. - eg: emit Issue(_operator, 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,[IERC3475.Transaction(1,14,500)]); - issue by address(operator) 500 Bonds(nonce14,class 0) to address 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. - */ - event Issue(address indexed _operator, address indexed _to, Transaction[] _transactions); - /** - * @notice MUST trigger when tokens are redeemed. - * @notice Redeem MUST trigger when Bonds are redeemed. This SHOULD not include zero value redemption. - * eg: emit Redeem(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,0x492Af743654549b12b1B807a9E0e8F397E44236E,[IERC3475.Transaction(1,14,500)]); - * this emit event when 5000 bonds of class 1, nonce 14 owned by address 0x492Af743654549b12b1B807a9E0e8F397E44236E are being redeemed by 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. - */ - event Redeem(address indexed _operator, address indexed _from, Transaction[] _transactions); - /** - * @notice MUST trigger when tokens are burned - * @dev `Burn` MUST trigger when the bonds are being redeemed via staking (or being invalidated) by the bank contract. - * @dev `Burn` MUST trigger when Bonds are burned. This SHOULD not include zero value burning - * @notice emit Burn(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,0x492Af743654549b12b1B807a9E0e8F397E44236E,[IERC3475.Transaction(1,14,500)]); - * emits event when 5000 bonds of owner 0x492Af743654549b12b1B807a9E0e8F397E44236E of type (class 1, nonce 14) are burned by operator 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. - */ - event Burn(address indexed _operator, address indexed _from, Transaction[] _transactions); - /** - * @dev MUST emit when approval for a second party/operator address to manage all bonds from a classId given for an owner address is enabled or disabled (absence of an event assumes disabled). - * @dev its emitted when address(_owner) approves the address(_operator) to transfer his bonds. - * @notice Approval MUST trigger when bond holders are approving an _operator. This SHOULD not include zero value approval. - */ - event ApprovalFor(address indexed _owner, address indexed _operator, bool _approved); -} diff --git a/assets/eip-3525/README.md b/assets/eip-3525/README.md deleted file mode 100644 index a2c57b6..0000000 --- a/assets/eip-3525/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# EIP-3525 - -## Demonstration only - -The code included in this directory is ONLY for the purpose of demonstrating how to implement this proposal, it is not a full-featured implementation ready for production. - -So please DO NOT use the code here for purposes other than study. diff --git a/assets/eip-3525/contracts/ERC3525.sol b/assets/eip-3525/contracts/ERC3525.sol deleted file mode 100644 index b955f69..0000000 --- a/assets/eip-3525/contracts/ERC3525.sol +++ /dev/null @@ -1,223 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "ERC721Enumerable.sol"; -import "./interface/IERC3525.sol"; -import "./interface/IERC3525Metadata.sol"; -import "./interface/IERC3525Receiver.sol"; - -contract ERC3525 is IERC3525, IERC3525Metadata, ERC721Enumerable { - using Address for address; - using Strings for uint256; - - struct ApproveData { - address[] approvals; - mapping(address => uint256) allowances; - } - - /// @dev tokenId => values - mapping(uint256 => uint256) internal _values; - - /// @dev tokenId => operator => units - mapping(uint256 => ApproveData) private _approvedValues; - - /// @dev tokenId => slot - mapping(uint256 => uint256) internal _slots; - - string private _name; - string private _symbol; - uint8 private _decimals; - - constructor( string memory name_, string memory symbol_, uint8 decimals_) ERC721(name_, symbol_) { - _decimals = decimals_; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721Enumerable) returns (bool) - { - return - interfaceId == type(IERC3525).interfaceId || - interfaceId == type(IERC3525Metadata).interfaceId || - super.supportsInterface(interfaceId); - } - - function valueDecimals() public view virtual override returns (uint8) { - return _decimals; - } - - function balanceOf(uint256 tokenId_) public view virtual override returns (uint256) - { - require( _exists(tokenId_), "ERC3525: balance query for nonexistent token"); - return _values[tokenId_]; - } - - function slotOf(uint256 tokenId_) public view virtual override returns (uint256) - { - require(_exists(tokenId_), "ERC3525: slot query for nonexistent token"); - return _slots[tokenId_]; - } - - function contractURI() public view virtual override returns (string memory) - { - string memory baseURI = _baseURI(); - return - bytes(baseURI).length > 0 - ? string( abi.encodePacked( baseURI, "contract/", Strings.toHexString(uint256(uint160(address(this)))))) : ""; - } - - function slotURI(uint256 slot_) public view virtual override returns (string memory) - { - string memory baseURI = _baseURI(); - return - bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, "slot/", slot_.toString())) : ""; - } - - function approve( uint256 tokenId_, address to_, uint256 value_) external payable virtual override { - address owner = ERC721.ownerOf(tokenId_); - require(to_ != owner, "ERC3525: approval to current owner"); - - require( - ERC721._isApprovedOrOwner(_msgSender(), tokenId_), - "ERC3525: approve caller is not owner nor approved for all" - ); - - _approveValue(tokenId_, to_, value_); - } - - function allowance(uint256 tokenId_, address operator_) public view virtual override returns (uint256) - { - return _approvedValues[tokenId_].allowances[operator_]; - } - - function transferFrom( uint256 fromTokenId_, address to_, uint256 value_) public payable virtual override returns (uint256) { - _spendAllowance(_msgSender(), fromTokenId_, value_); - - uint256 newTokenId = _getNewTokenId(fromTokenId_); - _mint(to_, newTokenId, _slots[fromTokenId_]); - _transfer(fromTokenId_, newTokenId, value_); - - return newTokenId; - } - - function transferFrom( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) public payable virtual override { - _spendAllowance(_msgSender(), fromTokenId_, value_); - - _transfer(fromTokenId_, toTokenId_, value_); - } - - function _mint( address to_, uint256 tokenId_, uint256 slot_) private { - ERC721._mint(to_, tokenId_); - _slots[tokenId_] = slot_; - emit SlotChanged(tokenId_, 0, slot_); - } - - function _mintValue( address to_, uint256 tokenId_, uint256 slot_, uint256 value_) internal virtual { - require(to_ != address(0), "ERC3525: mint to the zero address"); - require(tokenId_ != 0, "ERC3525: cannot mint zero tokenId"); - require(!_exists(tokenId_), "ERC3525: token already minted"); - - _mint(to_, tokenId_, slot_); - - _beforeValueTransfer(address(0), to_, 0, tokenId_, slot_, value_); - _values[tokenId_] = value_; - _afterValueTransfer(address(0), to_, 0, tokenId_, slot_, value_); - - emit TransferValue(0, tokenId_, value_); - } - - function _burn(uint256 tokenId_) internal virtual override { - address owner = ERC721.ownerOf(tokenId_); - ERC721._burn(tokenId_); - - uint256 slot = _slots[tokenId_]; - uint256 value = _values[tokenId_]; - - _beforeValueTransfer(owner, address(0), tokenId_, 0, slot, value); - delete _slots[tokenId_]; - delete _values[tokenId_]; - _afterValueTransfer(owner, address(0), tokenId_, 0, slot, value); - - emit TransferValue(tokenId_, 0, value); - emit SlotChanged(tokenId_, slot, 0); - } - - function _transfer( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) internal virtual { - require( _exists(fromTokenId_), - "ERC35255: transfer from nonexistent token"); - require(_exists(toTokenId_), "ERC35255: transfer to nonexistent token"); - - require( _values[fromTokenId_] >= value_, - "ERC3525: transfer amount exceeds balance"); - require( _slots[fromTokenId_] == _slots[toTokenId_], - "ERC3535: transfer to token with different slot"); - - address from = ERC721.ownerOf(fromTokenId_); - address to = ERC721.ownerOf(toTokenId_); - _beforeValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); - - _values[fromTokenId_] -= value_; - _values[toTokenId_] += value_; - - _afterValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); - - emit TransferValue(fromTokenId_, toTokenId_, value_); - } - - function _spendAllowance( address operator_, uint256 tokenId_, uint256 value_) internal virtual { - uint256 currentAllowance = ERC3525.allowance(tokenId_, operator_); - if ( !_isApprovedOrOwner(operator_, tokenId_) && currentAllowance != type(uint256).max) { - require( currentAllowance >= value_, "ERC3525: insufficient allowance"); - _approveValue(tokenId_, operator_, currentAllowance - value_); - } - } - - function _approveValue( uint256 tokenId_, address to_, uint256 value_) internal virtual { - ApproveData storage approveData = _approvedValues[tokenId_]; - approveData.approvals.push(to_); - approveData.allowances[to_] = value_; - - emit ApprovalValue(tokenId_, to_, value_); - } - - function _getNewTokenId(uint256 fromTokenId_) internal virtual returns (uint256) - { - return ERC721Enumerable.totalSupply() + 1; - } - - function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override { - //clear approve data - uint256 length = _approvedValues[tokenId].approvals.length; - for (uint256 i = 0; i < length; i++) { - address approval = _approvedValues[tokenId].approvals[i]; - delete _approvedValues[tokenId].allowances[approval]; - } - delete _approvedValues[tokenId].approvals; - } - - function _afterTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {} - - function _checkOnERC3525Received( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_, bytes memory data_) private returns (bool) { - address to = ERC721.ownerOf((toTokenId_)); - if (to.isContract() && IERC165(to).supportsInterface(type(IERC3525Receiver).interfaceId)) { - try - IERC3525Receiver(to).onERC3525Received( _msgSender(), fromTokenId_, toTokenId_, value_, data_) - returns (bytes4 retval) { - return retval == IERC3525Receiver.onERC3525Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert( "ERC3525: transfer to non ERC3525Receiver implementer"); - } else { - // solhint-disable-next-line - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - function _beforeValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, uint256 value_) internal virtual {} - - function _afterValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, uint256 value_) internal virtual {} -} diff --git a/assets/eip-3525/contracts/ERC3525Example.sol b/assets/eip-3525/contracts/ERC3525Example.sol deleted file mode 100644 index 16df657..0000000 --- a/assets/eip-3525/contracts/ERC3525Example.sol +++ /dev/null @@ -1,154 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "./ERC3525.sol"; -import "StringConvertor.sol"; -import "base64.sol"; - -/** - * This is a demo contract for how to generate slot - */ -contract ERC3525Example is ERC3525 { - using StringConvertor for uint256; - - /** - * @notice Properties of the slot, which determine the value of slot. - */ - struct SlotDetail { - string name; - string description; - string image; - address underlying; - uint8 vestingType; - uint32 maturity; - uint32 term; - } - - // slot => slotDetail - mapping(uint256 => SlotDetail) private _slotDetails; - - uint256 constant _externalMintMaxId = 1000000000; - - constructor( string memory name_, string memory symbol_, uint8 decimals_) ERC3525(name_, symbol_, decimals_) {} - - function mint( string memory slotName_, string memory slotDescription_, string memory slotImage_, - uint256 tokenId_, address underlying_, uint8 vestingType_, uint32 maturity_, uint32 term_, uint256 value_) public { - require(tokenId_ < _externalMintMaxId, "ERC3525: tokenId is too large"); - uint256 slot = _getSlot(underlying_, vestingType_, maturity_, term_); - _slotDetails[slot] = SlotDetail({ - name: slotName_, - description: slotDescription_, - image: slotImage_, - underlying: underlying_, - vestingType: vestingType_, - maturity: maturity_, - term: term_ - }); - - ERC3525._mintValue(_msgSender(), tokenId_, slot, value_); - } - - function getSlotDetail(uint256 slot_) public view returns (SlotDetail memory) { - return _slotDetails[slot_]; - } - - function _getNewTokenId(uint256 fromTokenId_) internal virtual override returns (uint256) { - return 1000000000 + fromTokenId_; - } - - /** - * @dev Generate the value of slot by utilizing keccak256 algorithm to calculate the hash - * value of multi properties. - */ - function _getSlot( address underlying_, uint8 vestingType_, uint32 maturity_, uint32 term_) internal pure virtual returns (uint256 slot_) { - return - uint256( - keccak256( - abi.encodePacked( - underlying_, - vestingType_, - maturity_, - term_ - ) - ) - ); - } - - function slotURI(uint256 slot_) public view virtual override returns (string memory) { - return - string( - abi.encodePacked( - /* solhint-disable */ - "data:application/json;base64,", - Base64.encode( - abi.encodePacked( - '{"name":"', - _slotDetails[slot_].name, - '","description":"', - _slotDetails[slot_].description, - '","image":"', - _slotDetails[slot_].image, - '","properties":', - _slotProperties(slot_), - "}" - ) - ) - /* solhint-enable */ - ) - ); - } - - /** - * @dev Generate the content of the `properties` field of `slotURI`. - */ - function _slotProperties(uint256 slot_) internal view returns (string memory) { - SlotDetail storage slotDetail = _slotDetails[slot_]; - return - string( - /* solhint-disable */ - abi.encodePacked( - "[", - abi.encodePacked( - '{"name":"underlying",', - '"description":"Address of the underlying token locked in this contract.",', - '"value":"', - Strings.toHexString( - uint256(uint160(slotDetail.underlying)) - ), - '",', - '"order":1,', - '"display_type":"string"},' - ), - abi.encodePacked( - '{"name":"vesting_type",', - '"description":"Vesting type that represents the releasing mode of underlying assets.",', - '"value":', - uint256(slotDetail.vestingType).toString(), - ",", - '"order":2,', - '"display_type":"number"},' - ), - abi.encodePacked( - '{"name":"maturity",', - '"description":"Maturity that all underlying assets would be completely released.",', - '"value":', - uint256(slotDetail.maturity).toString(), - ",", - '"order":3,', - '"display_type":"date"},' - ), - abi.encodePacked( - '{"name":"term",', - '"description":"The length of the locking period (in seconds)",', - '"value":', - uint256(slotDetail.term).toString(), - ",", - '"order":4,', - '"display_type":"number"}' - ), - "]" - ) - /* solhint-enable */ - ); - } -} diff --git a/assets/eip-3525/contracts/ERC3525SlotApprovable.sol b/assets/eip-3525/contracts/ERC3525SlotApprovable.sol deleted file mode 100644 index 871df34..0000000 --- a/assets/eip-3525/contracts/ERC3525SlotApprovable.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC3525.sol"; -import "./interface/IERC3525SlotApprovable.sol"; - -abstract contract ERC3525SlotApprovable is ERC3525, IERC3525SlotApprovable { - // @dev owner => slot => operator => approved - mapping(address => mapping(uint256 => mapping(address => bool))) - private _slotApprovals; - - function setApprovalForSlot( address owner_, uint256 slot_, address operator_, bool approved_) external payable virtual override { - require( - _msgSender() == owner_ || isApprovedForAll(owner_, _msgSender()), - "ERC3525SlotApprovable: caller is not owner nor approved for all" - ); - _setApprovalForSlot(owner_, slot_, operator_, approved_); - } - - function isApprovedForSlot( address owner_, uint256 slot_, address operator_) public view virtual override returns (bool) { - return _slotApprovals[owner_][slot_][operator_]; - } - - function approve(address to_, uint256 tokenId_) public virtual override(IERC721, ERC721) { - address owner = ERC721.ownerOf(tokenId_); - uint256 slot = ERC3525.slotOf(tokenId_); - require(to_ != owner, "ERC3525: approval to current owner"); - - require( - _msgSender() == owner || - ERC721.isApprovedForAll(owner, _msgSender()) || - ERC3525SlotApprovable.isApprovedForSlot( - owner, - slot, - _msgSender() - ), - "ERC3525: caller is not owner nor approved" - ); - - _approve(to_, tokenId_); - } - - function approve(uint256 tokenId_, address to_, uint256 value_) external payable virtual override(IERC3525, ERC3525) { - address owner = ERC721.ownerOf(tokenId_); - require(to_ != owner, "ERC3525: approval to current owner"); - - require( - _isApprovedOrOwner(_msgSender(), tokenId_), - "ERC3525: caller is not owner nor approved" - ); - - _approveValue(tokenId_, to_, value_); - } - - function _setApprovalForSlot( address owner_, uint256 slot_, address operator_, bool approved_) internal virtual { - require(owner_ != operator_, "ERC3525SlotApprovable: approve to owner"); - _slotApprovals[owner_][slot_][operator_] = approved_; - emit ApprovalForSlot(owner_, slot_, operator_, approved_); - } - - function _isApprovedOrOwner(address operator_, uint256 tokenId_) internal view virtual override returns (bool) { - require( - _exists(tokenId_), - "ERC3525: operator query for nonexistent token" - ); - address owner = ERC721.ownerOf(tokenId_); - uint256 slot = ERC3525.slotOf(tokenId_); - return (operator_ == owner || - getApproved(tokenId_) == operator_ || - ERC721.isApprovedForAll(owner, operator_) || - ERC3525SlotApprovable.isApprovedForSlot(owner, slot, operator_)); - } -} diff --git a/assets/eip-3525/contracts/ERC3525SlotEnumerable.sol b/assets/eip-3525/contracts/ERC3525SlotEnumerable.sol deleted file mode 100644 index 7be3711..0000000 --- a/assets/eip-3525/contracts/ERC3525SlotEnumerable.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC3525.sol"; -import "./interface/IERC3525SlotEnumerable.sol"; - -abstract contract ERC3525SlotEnumerable is ERC3525, IERC3525SlotEnumerable { - struct SlotData { - uint256 slot; - uint256[] slotTokens; - // mapping(uint256 => uint256) slotTokensIndex; - } - - // slot => tokenId => index - mapping(uint256 => mapping(uint256 => uint256)) private _slotTokensIndex; - - SlotData[] private _allSlots; - - // slot => index - mapping(uint256 => uint256) private _allSlotsIndex; - - function slotCount() public view virtual override returns (uint256) { - return _allSlots.length; - } - - function slotByIndex(uint256 index_) public view virtual override returns (uint256) { - require( - index_ < ERC3525SlotEnumerable.slotCount(), - "ERC3525SlotEnumerable: slot index out of bounds" - ); - return _allSlots[index_].slot; - } - - function _slotExists(uint256 slot_) internal view virtual returns (bool) { - return - _allSlots.length != 0 && - _allSlots[_allSlotsIndex[slot_]].slot == slot_; - } - - function tokenSupplyInSlot(uint256 slot_) public view virtual override returns (uint256) { - if (!_slotExists(slot_)) { - return 0; - } - return _allSlots[_allSlotsIndex[slot_]].slotTokens.length; - } - - function tokenInSlotByIndex(uint256 slot_, uint256 index_) public view virtual override returns (uint256) { - require( - index_ < ERC3525SlotEnumerable.tokenSupplyInSlot(slot_), - "ERC3525SlotEnumerable: slot token index out of bounds" - ); - return _allSlots[_allSlotsIndex[slot_]].slotTokens[index_]; - } - - function _tokenExistsInSlot(uint256 slot_, uint256 tokenId_) private view returns (bool) { - SlotData storage slotData = _allSlots[_allSlotsIndex[slot_]]; - return - slotData.slotTokens.length > 0 && - slotData.slotTokens[_slotTokensIndex[slot_][tokenId_]] == tokenId_; - } - - function _beforeValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, - uint256 value_) internal virtual override { - if (from_ == address(0) && fromTokenId_ == 0 && !_slotExists(slot_)) { - SlotData memory slotData = SlotData({ - slot: slot_, - slotTokens: new uint256[](0) - }); - _addSlotToAllSlotsEnumeration(slotData); - } - - //Shh - currently unused - to_; - toTokenId_; - value_; - } - - function _afterValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, - uint256 value_) internal virtual override { - if ( - from_ == address(0) && - fromTokenId_ == 0 && - !_tokenExistsInSlot(slot_, toTokenId_) - ) { - _addTokenToSlotEnumeration(slot_, toTokenId_); - } else if ( - to_ == address(0) && - toTokenId_ == 0 && - _tokenExistsInSlot(slot_, fromTokenId_) - ) { - _removeTokenFromSlotEnumeration(slot_, fromTokenId_); - } - - //Shh - currently unused - value_; - } - - function _addSlotToAllSlotsEnumeration(SlotData memory slotData) private { - _allSlotsIndex[slotData.slot] = _allSlots.length; - _allSlots.push(slotData); - } - - function _addTokenToSlotEnumeration(uint256 slot_, uint256 tokenId_) private { - SlotData storage slotData = _allSlots[_allSlotsIndex[slot_]]; - _slotTokensIndex[slot_][tokenId_] = slotData.slotTokens.length; - slotData.slotTokens.push(tokenId_); - } - - function _removeTokenFromSlotEnumeration(uint256 slot_, uint256 tokenId_) private { - SlotData storage slotData = _allSlots[_allSlotsIndex[slot_]]; - uint256 lastTokenIndex = slotData.slotTokens.length - 1; - uint256 lastTokenId = slotData.slotTokens[lastTokenIndex]; - uint256 tokenIndex = slotData.slotTokens[tokenId_]; - - slotData.slotTokens[tokenIndex] = lastTokenId; - _slotTokensIndex[slot_][lastTokenId] = tokenIndex; - - delete _slotTokensIndex[slot_][tokenId_]; - slotData.slotTokens.pop(); - } -} diff --git a/assets/eip-3525/contracts/interface/IERC3525.sol b/assets/eip-3525/contracts/interface/IERC3525.sol deleted file mode 100644 index d62af24..0000000 --- a/assets/eip-3525/contracts/interface/IERC3525.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "IERC165.sol"; -import "IERC721.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard - * @dev See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xc97ae3d5. - */ -interface IERC3525 is IERC165, IERC721 { - /** - * @dev MUST emit when value of a token is transferred to another token with the same slot, - * including zero value transfers (_value == 0) as well as transfers when tokens are created - * (`_fromTokenId` == 0) or destroyed (`_toTokenId` == 0). - * @param _fromTokenId The token id to transfer value from - * @param _toTokenId The token id to transfer value to - * @param _value The transferred value - */ - event TransferValue( uint256 indexed _fromTokenId, uint256 indexed _toTokenId, uint256 _value); - - /** - * @dev MUST emits when the approval value of a token is set or changed. - * @param _tokenId The token to approve - * @param _operator The operator to approve for - * @param _value The maximum value that `_operator` is allowed to manage - */ - event ApprovalValue( uint256 indexed _tokenId, address indexed _operator, uint256 _value); - - /** - * @dev MUST emit when the slot of a token is set or changed. - * @param _tokenId The token of which slot is set or changed - * @param _oldSlot The previous slot of the token - * @param _newSlot The updated slot of the token - */ - event SlotChanged( uint256 indexed _tokenId, uint256 indexed _oldSlot, uint256 indexed _newSlot); - - /** - * @notice Get the number of decimals the token uses for value - e.g. 6, means the user - * representation of the value of a token can be calculated by dividing it by 1,000,000. - * Considering the compatibility with third-party wallets, this function is defined as - * `valueDecimals()` instead of `decimals()` to avoid conflict with ERC20 tokens. - * @return The number of decimals for value - */ - function valueDecimals() external view returns (uint8); - - /** - * @notice Get the value of a token. - * @param _tokenId The token for which to query the balance - * @return The value of `_tokenId` - */ - function balanceOf(uint256 _tokenId) external view returns (uint256); - - /** - * @notice Get the slot of a token. - * @param _tokenId The identifier for a token - * @return The slot of the token - */ - function slotOf(uint256 _tokenId) external view returns (uint256); - - /** - * @notice Allow an operator to manage the value of a token, up to the `_value` amount. - * @dev MUST revert unless caller is the current owner, an authorized operator, or the approved - * address for `_tokenId`. - * MUST emit ApprovalValue event. - * @param _tokenId The token to approve - * @param _operator The operator to be approved - * @param _value The maximum value of `_toTokenId` that `_operator` is allowed to manage - */ - function approve( uint256 _tokenId, address _operator, uint256 _value) external payable; - - /** - * @notice Get the maximum value of a token that an operator is allowed to manage. - * @param _tokenId The token for which to query the allowance - * @param _operator The address of an operator - * @return The current approval value of `_tokenId` that `_operator` is allowed to manage - */ - function allowance(uint256 _tokenId, address _operator) external view returns (uint256); - - /** - * @notice Transfer value from a specified token to another specified token with the same slot. - * @dev Caller MUST be the current owner, an authorized operator or an operator who has been - * approved the whole `_fromTokenId` or part of it. - * MUST revert if `_fromTokenId` or `_toTokenId` is zero token id or does not exist. - * MUST revert if slots of `_fromTokenId` and `_toTokenId` do not match. - * MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the - * operator. - * MUST emit `TransferValue` event. - * @param _fromTokenId The token to transfer value from - * @param _toTokenId The token to transfer value to - * @param _value The transferred value - */ - function transferFrom( uint256 _fromTokenId, uint256 _toTokenId, uint256 _value) external payable; - - /** - * @notice Transfer value from a specified token to an address. The caller should confirm that - * `_to` is capable of receiving ERC3525 tokens. - * @dev This function MUST create a new ERC3525 token with the same slot for `_to` to receive - * the transferred value. - * MUST revert if `_fromTokenId` is zero token id or does not exist. - * MUST revert if `_to` is zero address. - * MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the - * operator. - * MUST emit `Transfer` and `TransferValue` events. - * @param _fromTokenId The token to transfer value from - * @param _to The address to transfer value to - * @param _value The transferred value - * @return ID of the new token created for `_to` which receives the transferred value - */ - function transferFrom( uint256 _fromTokenId, address _to, uint256 _value) external payable returns (uint256); -} diff --git a/assets/eip-3525/contracts/interface/IERC3525Metadata.sol b/assets/eip-3525/contracts/interface/IERC3525Metadata.sol deleted file mode 100644 index 6d90fba..0000000 --- a/assets/eip-3525/contracts/interface/IERC3525Metadata.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC3525.sol"; -import "IERC721Metadata.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for metadata - * @dev Interfaces for any contract that wants to support query of the Uniform Resource Identifier - * (URI) for the ERC3525 contract as well as a specified slot. - * Because of the higher reliability of data stored in smart contracts compared to data stored in - * centralized systems, it is recommended that metadata, including `contractURI`, `slotURI` and - * `tokenURI`, be directly returned in JSON format, instead of being returned with a url pointing - * to any resource stored in a centralized system. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xe1600902. - */ -interface IERC3525Metadata is IERC3525, IERC721Metadata { - /** - * @notice Returns the Uniform Resource Identifier (URI) for the current ERC3525 contract. - * @dev This function SHOULD return the URI for this contract in JSON format, starting with - * header `data:application/json;`. - * See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for contract URI. - * @return The JSON formatted URI of the current ERC3525 contract - */ - function contractURI() external view returns (string memory); - - /** - * @notice Returns the Uniform Resource Identifier (URI) for the specified slot. - * @dev This function SHOULD return the URI for `_slot` in JSON format, starting with header - * `data:application/json;`. - * See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for slot URI. - * @return The JSON formatted URI of `_slot` - */ - function slotURI(uint256 _slot) external view returns (string memory); -} diff --git a/assets/eip-3525/contracts/interface/IERC3525Receiver.sol b/assets/eip-3525/contracts/interface/IERC3525Receiver.sol deleted file mode 100644 index 86e0d61..0000000 --- a/assets/eip-3525/contracts/interface/IERC3525Receiver.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/** - * @title ERC3525 token receiver interface - * @dev Interface for any contract that wants to support safeTransfers from ERC3525 contracts. - * Note: the ERC-165 identifier for this interface is 0x009ce20b. - */ -interface IERC3525Receiver { - /** - * @notice Handle the receipt of an ERC3525 token value. - * @dev An ERC3525 smart contract MUST call this function on the recipient contract after a - * value transfer (i.e. `safeTransferFrom(uint256,uint256,uint256,bytes)`). - * MUST return 0x009ce20b (i.e. `bytes4(keccak256('onERC3525Received(address,uint256,uint256, - * uint256,bytes)'))`) if the transfer is accepted. - * MUST revert or return any value other than 0x009ce20b if the transfer is rejected. - * @param _operator The address which triggered the transfer - * @param _fromTokenId The token id to transfer value from - * @param _toTokenId The token id to transfer value to - * @param _value The transferred value - * @param _data Additional data with no specified format - * @return `bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)'))` - * unless the transfer is rejected. - */ - function onERC3525Received(address _operator, uint256 _fromTokenId, uint256 _toTokenId, uint256 _value, bytes calldata _data) external returns (bytes4); -} \ No newline at end of file diff --git a/assets/eip-3525/contracts/interface/IERC3525SlotApprovable.sol b/assets/eip-3525/contracts/interface/IERC3525SlotApprovable.sol deleted file mode 100644 index 1bfea70..0000000 --- a/assets/eip-3525/contracts/interface/IERC3525SlotApprovable.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC3525.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for approval of slot level - * @dev Interfaces for any contract that wants to support approval of slot level, which allows an - * operator to manage one's tokens with the same slot. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xb688be58. - */ -interface IERC3525SlotApprovable is IERC3525 { - /** - * @dev MUST emits when an operator is approved or disapproved to manage all of `_owner`'s - * tokens with the same slot. - * @param _owner The address whose tokens are approved - * @param _slot The slot to approve, all of `_owner`'s tokens with this slot are approved - * @param _operator The operator being approved or disapproved - * @param _approved Identify if `_operator` is approved or disapproved - */ - event ApprovalForSlot( address indexed _owner, uint256 indexed _slot, address indexed _operator, bool _approved); - - /** - * @notice Approve or disapprove an operator to manage all of `_owner`'s tokens with the - * specified slot. - * @dev Caller SHOULD be `_owner` or an operator who has been authorized through - * `setApprovalForAll`. - * MUST emit ApprovalSlot event. - * @param _owner The address that owns the ERC3525 tokens - * @param _slot The slot of tokens being queried approval of - * @param _operator The address for whom to query approval - * @param _approved Identify if `_operator` would be approved or disapproved - */ - function setApprovalForSlot( address _owner, uint256 _slot, address _operator, bool _approved) external payable; - - /** - * @notice Query if `_operator` is authorized to manage all of `_owner`'s tokens with the - * specified slot. - * @param _owner The address that owns the ERC3525 tokens - * @param _slot The slot of tokens being queried approval of - * @param _operator The address for whom to query approval - * @return True if `_operator` is authorized to manage all of `_owner`'s tokens with `_slot`, - * false otherwise. - */ - function isApprovedForSlot( address _owner, uint256 _slot, address _operator) external view returns (bool); -} diff --git a/assets/eip-3525/contracts/interface/IERC3525SlotEnumerable.sol b/assets/eip-3525/contracts/interface/IERC3525SlotEnumerable.sol deleted file mode 100644 index f10086f..0000000 --- a/assets/eip-3525/contracts/interface/IERC3525SlotEnumerable.sol +++ /dev/null @@ -1,42 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC3525.sol"; -import "IERC721Enumerable.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for slot enumeration - * @dev Interfaces for any contract that wants to support enumeration of slots as well as tokens - * with the same slot. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0x3b741b9e. - */ -interface IERC3525SlotEnumerable is IERC3525, IERC721Enumerable { - /** - * @notice Get the total amount of slots stored by the contract. - * @return The total amount of slots - */ - function slotCount() external view returns (uint256); - - /** - * @notice Get the slot at the specified index of all slots stored by the contract. - * @param _index The index in the slot list - * @return The slot at `index` of all slots. - */ - function slotByIndex(uint256 _index) external view returns (uint256); - - /** - * @notice Get the total amount of tokens with the same slot. - * @param _slot The slot to query token supply for - * @return The total amount of tokens with the specified `_slot` - */ - function tokenSupplyInSlot(uint256 _slot) external view returns (uint256); - - /** - * @notice Get the token at the specified index of all tokens with the same slot. - * @param _slot The slot to query tokens with - * @param _index The index in the token list of the slot - * @return The token ID at `_index` of all tokens with `_slot` - */ - function tokenInSlotByIndex(uint256 _slot, uint256 _index) external view returns (uint256); -} diff --git a/assets/eip-3525/test/test.ts b/assets/eip-3525/test/test.ts deleted file mode 100644 index 4ca5f3a..0000000 --- a/assets/eip-3525/test/test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { BigNumber } from "ethers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { ERC3525Example } from "../typechain"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; - -const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; -const ZERO_TOKEN_ID = 0; - -let owner: SignerWithAddress, - approval: SignerWithAddress, - to: SignerWithAddress; -let token: ERC3525Example; -let snapshotId: any; -const fromTokenId = 35251; -const toTokenId = 35252; -const fromValue = 10000000000; -const approveValue = 5000000000; -const transferValue = 3000000000; -const slotDetails = { - name: "Test Slot", - description: "Test Slot Description", - image: "https://example.com/slot/test_slot.png", - underlying: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - vestingType: 1, - maturity: 1658989800, - term: 2592000, - value: fromValue, -}; - -describe("ERC3525", function () { - before(async () => { - const signers = await ethers.getSigners(); - owner = signers[0]; - approval = signers[1]; - to = signers[2]; - const ERC3525Factory = await ethers.getContractFactory("ERC3525Example"); - token = (await ERC3525Factory.deploy( - "TST", - "Test 3525", - 18 - )) as ERC3525Example; - await token.deployed(); - - await token.mint( - slotDetails.name, - slotDetails.description, - slotDetails.image, - fromTokenId, - slotDetails.underlying, - slotDetails.vestingType, - slotDetails.maturity, - slotDetails.term, - slotDetails.value - ); - - await token.mint( - slotDetails.name, - slotDetails.description, - slotDetails.image, - toTokenId, - slotDetails.underlying, - slotDetails.vestingType, - slotDetails.maturity, - slotDetails.term, - 0 - ); - }); - - beforeEach(async function () { - snapshotId = await ethers.provider.send("evm_snapshot", []); - }); - - afterEach(async function () { - await ethers.provider.send("evm_revert", [snapshotId]); - }); - - describe("ERC3525 Example", function () { - it("approve value should be success", async () => { - await token["approve(uint256,address,uint256)"]( - fromTokenId, - approval.address, - approveValue - ); - - expect(await token.allowance(fromTokenId, approval.address)).to.eq( - approveValue - ); - }); - - it("transfer value to id should be success", async () => { - expect( - await token["transferFrom(uint256,uint256,uint256)"]( - fromTokenId, - toTokenId, - transferValue - ) - ); - expect(await token["balanceOf(uint256)"](fromTokenId)).to.eq( - fromValue - transferValue - ); - expect(await token["balanceOf(uint256)"](toTokenId)).to.eq(transferValue); - }); - - it("transfer value to address should be success", async () => { - expect( - await token["transferFrom(uint256,address,uint256)"]( - fromTokenId, - to.address, - transferValue - ) - ); - expect(await token["balanceOf(uint256)"](fromTokenId)).to.eq( - fromValue - transferValue - ); - const newTokenId = 1000000000 + fromTokenId; - expect(await token["balanceOf(uint256)"](newTokenId)).to.eq( - transferValue - ); - }); - - it("approved value should be correct after transfer value to id", async () => { - await token["approve(uint256,address,uint256)"]( - fromTokenId, - approval.address, - approveValue - ); - const approvalToken = token.connect(approval); - await approvalToken["transferFrom(uint256,uint256,uint256)"]( - fromTokenId, - toTokenId, - transferValue - ); - expect(await token.allowance(fromTokenId, approval.address)).to.eq( - approveValue - transferValue - ); - }); - }); -}); diff --git a/assets/eip-3607/geth.diff b/assets/eip-3607/geth.diff deleted file mode 100644 index ea65164..0000000 --- a/assets/eip-3607/geth.diff +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/core/state_transition.go b/core/state_transition.go -index 18777d8d4..3b25155c6 100644 ---- a/core/state_transition.go -+++ b/core/state_transition.go -@@ -219,6 +219,11 @@ func (st *StateTransition) preCheck() error { - st.msg.From().Hex(), msgNonce, stNonce) - } - } -+ // Make sure the sender is an EOA -+ if codeHash := st.state.GetCodeHash(st.msg.From()); codeHash != emptyCodeHash { -+ return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, -+ st.msg.From().Hex(), codeHash) -+ } - // Make sure that transaction feeCap is greater than the baseFee (post london) - if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { - if l := st.feeCap.BitLen(); l > 256 { \ No newline at end of file diff --git a/assets/eip-3770/examples.png b/assets/eip-3770/examples.png deleted file mode 100644 index 0793b6b..0000000 Binary files a/assets/eip-3770/examples.png and /dev/null differ diff --git a/assets/eip-4337/image1.png b/assets/eip-4337/image1.png deleted file mode 100644 index ced8ea5..0000000 Binary files a/assets/eip-4337/image1.png and /dev/null differ diff --git a/assets/eip-4337/image2.png b/assets/eip-4337/image2.png deleted file mode 100644 index 6d0c6f7..0000000 Binary files a/assets/eip-4337/image2.png and /dev/null differ diff --git a/assets/eip-4361/example.js b/assets/eip-4361/example.js deleted file mode 100644 index 12ec7ee..0000000 --- a/assets/eip-4361/example.js +++ /dev/null @@ -1,238 +0,0 @@ -// To run this example, navigate to this directory and run `npm i && node example.js` - -const apgApi = require('apg-js/src/apg-api/api'); -const apgLib = require('apg-js/src/apg-lib/node-exports'); - -const GRAMMAR = ` -sign-in-with-ethereum = - domain %s" wants you to sign in with your Ethereum account:" LF - address LF - LF - [ statement LF ] - LF - %s"URI: " URI LF - %s"Version: " version LF - %s"Chain ID: " chain-id LF - %s"Nonce: " nonce LF - %s"Issued At: " issued-at - [ LF %s"Expiration Time: " expiration-time ] - [ LF %s"Not Before: " not-before ] - [ LF %s"Request ID: " request-id ] - [ LF %s"Resources:" - resources ] - -domain = authority - -address = "0x" 40*40HEXDIG - ; Must also conform to captilization - ; checksum encoding specified in EIP-55 - ; where applicable (EOAs). - -statement = 1*( reserved / unreserved / " " ) - ; The purpose is to exclude LF (line breaks). - -version = "1" - -nonce = 8*( ALPHA / DIGIT ) - -issued-at = date-time -expiration-time = date-time -not-before = date-time - -request-id = *pchar - -chain-id = 1*DIGIT - ; See EIP-155 for valid CHAIN_IDs. - -resources = *( LF resource ) - -resource = "- " URI - -; ------------------------------------------------------------------------------ -; RFC 3986 - -URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] - -hier-part = "//" authority path-abempty - / path-absolute - / path-rootless - / path-empty - -scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - -authority = [ userinfo "@" ] host [ ":" port ] -userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) -host = IP-literal / IPv4address / reg-name -port = *DIGIT - -IP-literal = "[" ( IPv6address / IPvFuture ) "]" - -IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) - -IPv6address = 6( h16 ":" ) ls32 - / "::" 5( h16 ":" ) ls32 - / [ h16 ] "::" 4( h16 ":" ) ls32 - / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 - / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 - / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 - / [ *4( h16 ":" ) h16 ] "::" ls32 - / [ *5( h16 ":" ) h16 ] "::" h16 - / [ *6( h16 ":" ) h16 ] "::" - -h16 = 1*4HEXDIG -ls32 = ( h16 ":" h16 ) / IPv4address -IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet -dec-octet = DIGIT ; 0-9 - / %x31-39 DIGIT ; 10-99 - / "1" 2DIGIT ; 100-199 - / "2" %x30-34 DIGIT ; 200-249 - / "25" %x30-35 ; 250-255 - -reg-name = *( unreserved / pct-encoded / sub-delims ) - -path-abempty = *( "/" segment ) -path-absolute = "/" [ segment-nz *( "/" segment ) ] -path-rootless = segment-nz *( "/" segment ) -path-empty = 0pchar - -segment = *pchar -segment-nz = 1*pchar - -pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - -query = *( pchar / "/" / "?" ) - -fragment = *( pchar / "/" / "?" ) - -pct-encoded = "%" HEXDIG HEXDIG - -unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -reserved = gen-delims / sub-delims -gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" -sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - / "*" / "+" / "," / ";" / "=" - -; ------------------------------------------------------------------------------ -; RFC 3339 - -date-fullyear = 4DIGIT -date-month = 2DIGIT ; 01-12 -date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on - ; month/year -time-hour = 2DIGIT ; 00-23 -time-minute = 2DIGIT ; 00-59 -time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second - ; rules -time-secfrac = "." 1*DIGIT -time-numoffset = ("+" / "-") time-hour ":" time-minute -time-offset = "Z" / time-numoffset - -partial-time = time-hour ":" time-minute ":" time-second - [time-secfrac] -full-date = date-fullyear "-" date-month "-" date-mday -full-time = partial-time time-offset - -date-time = full-date "T" full-time - -; ------------------------------------------------------------------------------ -; RFC 5234 - -ALPHA = %x41-5A / %x61-7A ; A-Z / a-z -LF = %x0A - ; linefeed -DIGIT = %x30-39 - ; 0-9 -HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" -`; - -const parseMessage = (message) => { - const api = new apgApi(GRAMMAR); - api.generate(); - - const grammarObj = api.toObject(); - const parser = new apgLib.parser(); - parser.ast = new apgLib.ast(); - const id = apgLib.ids; - - const charToString = apgLib.utils.charsToString; - - const getField = (field) => function (state, chars, phraseIndex, phraseLength, data) { - const ret = id.SEM_OK; - if (state === id.SEM_PRE) { - data[field] = charToString(chars, phraseIndex, phraseLength); - } - return ret; - }; - - const domain = getField("domain"); - parser.ast.callbacks.domain = domain; - const address = getField("address"); - parser.ast.callbacks.address = address; - const statement = getField("statement"); - parser.ast.callbacks.statement = statement; - const uri = getField("uri"); - parser.ast.callbacks.uri = uri; - const version = getField("version"); - parser.ast.callbacks.version = version; - const chainId = getField("chainId"); - parser.ast.callbacks['chain-id'] = chainId; - const nonce = getField("nonce"); - parser.ast.callbacks.nonce = nonce; - const issuedAt = getField("issuedAt"); - parser.ast.callbacks['issued-at'] = issuedAt; - const expirationTime = getField("expirationTime"); - parser.ast.callbacks['expiration-time'] = expirationTime; - const notBefore = getField("notBefore"); - parser.ast.callbacks['not-before'] = notBefore; - const requestId = getField("requestId"); - parser.ast.callbacks['request-id'] = requestId; - - const resources = function (state, chars, phraseIndex, phraseLength, data) { - const ret = id.SEM_OK; - if (state === id.SEM_PRE) { - data.resources = apgLib.utils - .charsToString(chars, phraseIndex, phraseLength) - .slice(3) - .split('\n- '); - } - return ret; - }; - parser.ast.callbacks.resources = resources; - - const result = parser.parse(grammarObj, 'sign-in-with-ethereum', message); - if (!result.success) { - throw new Error(`Invalid message: ${JSON.stringify(result)}`); - } - const elements = {}; - parser.ast.translate(elements); - let obj = {}; - for (const [key, value] of Object.entries(elements)) { - obj[key] = value; - } - return obj; -} - -const createMessage = ({ domain, address, uri, version, chainId, nonce, issuedAt }) => { - const header = `${domain} wants you to sign in with your Ethereum account:\n${address}\n\n\n`; - const uriField = `URI: ${uri}\n`; - const versionField = `Version: ${version}\n`; - const chainField = `Chain ID: ${chainId}\n`; - const nonceField = `Nonce: ${nonce}\n`; - const issuedAtField = `Issued At: ${issuedAt}`; - return [header, uriField, versionField, chainField, nonceField, issuedAtField].join(''); -} - -const message = createMessage({ - domain: "example.com", - address: "0x51e913F93EBBF41f0DAc68219c31c1c15DFe3C49", - uri: "https://example.com", - version: '1', - chainId: '1', - nonce: "Ap4xKpGjEcYkYubmH4Vpw7pW8b3s6cJd", - issuedAt: "2022-05-18T14:11:46.065Z", -}); - -const messageObject = parseMessage(message); - -console.log(message, '\n'); -console.log(messageObject); diff --git a/assets/eip-4361/package.json b/assets/eip-4361/package.json deleted file mode 100644 index 1819473..0000000 --- a/assets/eip-4361/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "eip4361-example", - "version": "1.0.0", - "description": "Reference implementation for Sign-In with Ethereum", - "main": "example.js", - "dependencies": { - "apg-js": "^4.1.1" - } -} diff --git a/assets/eip-4361/signing.png b/assets/eip-4361/signing.png deleted file mode 100644 index f92b4e6..0000000 Binary files a/assets/eip-4361/signing.png and /dev/null differ diff --git a/assets/eip-4396/degradation.png b/assets/eip-4396/degradation.png deleted file mode 100644 index cc2262a..0000000 Binary files a/assets/eip-4396/degradation.png and /dev/null differ diff --git a/assets/eip-4396/degradation_buffers.png b/assets/eip-4396/degradation_buffers.png deleted file mode 100644 index fd87694..0000000 Binary files a/assets/eip-4396/degradation_buffers.png and /dev/null differ diff --git a/assets/eip-4396/degradation_elasticity.png b/assets/eip-4396/degradation_elasticity.png deleted file mode 100644 index 29c95dd..0000000 Binary files a/assets/eip-4396/degradation_elasticity.png and /dev/null differ diff --git a/assets/eip-4396/new_formula.png b/assets/eip-4396/new_formula.png deleted file mode 100644 index 46934d3..0000000 Binary files a/assets/eip-4396/new_formula.png and /dev/null differ diff --git a/assets/eip-4396/old_formula.png b/assets/eip-4396/old_formula.png deleted file mode 100644 index 27f07ab..0000000 Binary files a/assets/eip-4396/old_formula.png and /dev/null differ diff --git a/assets/eip-4400/.gitignore b/assets/eip-4400/.gitignore deleted file mode 100644 index 6d1e7e0..0000000 --- a/assets/eip-4400/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules - -#Hardhat files -cache -artifacts -typechain diff --git a/assets/eip-4400/LICENSE b/assets/eip-4400/LICENSE deleted file mode 100644 index 1625c17..0000000 --- a/assets/eip-4400/LICENSE +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. \ No newline at end of file diff --git a/assets/eip-4400/README.md b/assets/eip-4400/README.md deleted file mode 100644 index d62d9ea..0000000 --- a/assets/eip-4400/README.md +++ /dev/null @@ -1,29 +0,0 @@ -
- -# ERC721 Consumable Extension - -[![License: CC0-1.0](https://img.shields.io/badge/License-CC0-yellow.svg)](https://creativecommons.org/publicdomain/zero/1.0/) - -
- -This project provides a reference implementation of the proposed `ERC721Consumer` OPTIONAL extension. - -## Install - -In order to install the required dependencies you need to execute: -```shell -npm install -``` - -## Compile - -In order to compile the solidity contracts you need to execute: -```shell -npx hardhat compile -``` - -## Tests - -```shell -npx hardhat test -``` \ No newline at end of file diff --git a/assets/eip-4400/abi/ERC721Consumable.json b/assets/eip-4400/abi/ERC721Consumable.json deleted file mode 100644 index 87c8cde..0000000 --- a/assets/eip-4400/abi/ERC721Consumable.json +++ /dev/null @@ -1,410 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "name_", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol_", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "consumer", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ConsumerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_consumer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "changeConsumer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "consumerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "getApproved", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "tokenURI", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/assets/eip-4400/abi/IERC721Consumable.json b/assets/eip-4400/abi/IERC721Consumable.json deleted file mode 100644 index 8b0e6b8..0000000 --- a/assets/eip-4400/abi/IERC721Consumable.json +++ /dev/null @@ -1,349 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "consumer", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ConsumerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_consumer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "changeConsumer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "consumerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "getApproved", - "outputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "_approved", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/assets/eip-4400/contracts/ERC721Consumable.sol b/assets/eip-4400/contracts/ERC721Consumable.sol deleted file mode 100644 index 56912b9..0000000 --- a/assets/eip-4400/contracts/ERC721Consumable.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.11; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC721Consumable.sol"; - -contract ERC721Consumable is IERC721Consumable, ERC721 { - - // Mapping from token ID to consumer address - mapping(uint256 => address) _tokenConsumers; - - constructor (string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - /** - * @dev Returns true if the `msg.sender` is approved, owner or consumer of the `tokenId` - */ - function _isApprovedOwnerOrConsumer(uint256 tokenId) internal view returns (bool) { - return _isApprovedOrOwner(msg.sender, tokenId) || _tokenConsumers[tokenId] == msg.sender; - } - - /** - * @dev See {IERC721Consumable-consumerOf} - */ - function consumerOf(uint256 _tokenId) view external returns (address) { - require(_exists(_tokenId), "ERC721Consumable: consumer query for nonexistent token"); - return _tokenConsumers[_tokenId]; - } - - /** - * @dev See {IERC721Consumable-changeConsumer} - */ - function changeConsumer(address _consumer, uint256 _tokenId) external { - address owner = this.ownerOf(_tokenId); - require(msg.sender == owner || msg.sender == getApproved(_tokenId) || isApprovedForAll(owner, msg.sender), - "ERC721Consumable: changeConsumer caller is not owner nor approved"); - _changeConsumer(owner, _consumer, _tokenId); - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return interfaceId == type(IERC721Consumable).interfaceId || super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal virtual override (ERC721) { - super._beforeTokenTransfer(_from, _to, _tokenId); - _changeConsumer(_from, address(0), _tokenId); - } - - /** - * @dev Changes the consumer - * Requirement: `tokenId` must exist - */ - function _changeConsumer(address _owner, address _consumer, uint256 _tokenId) internal { - _tokenConsumers[_tokenId] = _consumer; - emit ConsumerChanged(_owner, _consumer, _tokenId); - } -} diff --git a/assets/eip-4400/contracts/ExampleToken.sol b/assets/eip-4400/contracts/ExampleToken.sol deleted file mode 100644 index 74f5b48..0000000 --- a/assets/eip-4400/contracts/ExampleToken.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.11; - -import "./ERC721Consumable.sol"; - -contract ExampleToken is ERC721Consumable { - - uint256 public idCounter = 0; - - constructor() ERC721Consumable("ReferenceImpl", "RIMPL") { } - - // @notice Mints new NFT to msg.sender - function mint() external returns (uint256) { - idCounter++; - _mint(msg.sender, idCounter); - return idCounter; - } -} diff --git a/assets/eip-4400/contracts/IERC721Consumable.sol b/assets/eip-4400/contracts/IERC721Consumable.sol deleted file mode 100644 index e03f737..0000000 --- a/assets/eip-4400/contracts/IERC721Consumable.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.11; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/// @title ERC-721 Consumer Role extension -/// Note: the ERC-165 identifier for this interface is 0x953c8dfa -interface IERC721Consumable is IERC721 { - - /// @notice Emitted when `owner` changes the `consumer` of an NFT - /// The zero address for consumer indicates that there is no consumer address - /// When a Transfer event emits, this also indicates that the consumer address - /// for that NFT (if any) is set to none - event ConsumerChanged(address indexed owner, address indexed consumer, uint256 indexed tokenId); - - /// @notice Get the consumer address of an NFT - /// @dev The zero address indicates that there is no consumer - /// Throws if `_tokenId` is not a valid NFT - /// @param _tokenId The NFT to get the consumer address for - /// @return The consumer address for this NFT, or the zero address if there is none - function consumerOf(uint256 _tokenId) view external returns (address); - - /// @notice Change or reaffirm the consumer address for an NFT - /// @dev The zero address indicates there is no consumer address - /// Throws unless `msg.sender` is the current NFT owner, an authorised - /// operator of the current owner or approved address - /// Throws if `_tokenId` is not valid NFT - /// @param _consumer The new consumer of the NFT - function changeConsumer(address _consumer, uint256 _tokenId) external; -} diff --git a/assets/eip-4400/hardhat.config.ts b/assets/eip-4400/hardhat.config.ts deleted file mode 100644 index da97857..0000000 --- a/assets/eip-4400/hardhat.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import '@nomiclabs/hardhat-waffle'; -import 'hardhat-abi-exporter'; -import 'hardhat-typechain'; - - -module.exports = { - solidity: { - compilers: [ - { - version: "0.8.11", - }, - ], - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - defaultNetwork: 'hardhat', - abiExporter: { - only: ['IERC721Consumable', 'ERC721Consumable'], - clear: true, - flat: true, - }, -}; diff --git a/assets/eip-4400/package.json b/assets/eip-4400/package.json deleted file mode 100644 index 3530cc7..0000000 --- a/assets/eip-4400/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "hardhat-project", - "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.2", - "@nomiclabs/hardhat-waffle": "^2.0.1", - "@typechain/ethers-v5": "^2.0.0", - "@types/chai": "^4.2.14", - "@types/mocha": "^8.0.3", - "@types/node": "^14.14.6", - "chai": "^4.3.4", - "ethereum-waffle": "^3.4.0", - "ethers": "^5.4.7", - "hardhat": "^2.6.7", - "hardhat-abi-exporter": "^2.3.1", - "hardhat-typechain": "^0.3.3", - "ts-node": "^10.4.0", - "typechain": "^3.0.0", - "typescript": "^4.5.2" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.3.2" - } -} diff --git a/assets/eip-4400/test/erc721-consumable.ts b/assets/eip-4400/test/erc721-consumable.ts deleted file mode 100644 index a65c6e3..0000000 --- a/assets/eip-4400/test/erc721-consumable.ts +++ /dev/null @@ -1,134 +0,0 @@ -import {ethers} from "hardhat"; -import {expect} from 'chai'; -import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; -import {Erc721Consumable} from "../typechain"; - -describe("ERC721Consumable", async () => { - let owner: SignerWithAddress, approved: SignerWithAddress, operator: SignerWithAddress, consumer: SignerWithAddress, - other: SignerWithAddress; - let token: Erc721Consumable; - let snapshotId: any; - const tokenID = 1; // The first minted NFT - - before(async () => { - const signers = await ethers.getSigners(); - owner = signers[0]; - approved = signers[1]; - operator = signers[2]; - consumer = signers[3]; - other = signers[4]; - - const ConsumableToken = await ethers.getContractFactory("ExampleToken"); - const deployedContract = await ConsumableToken.deploy(); - await deployedContract.deployed(); - token = deployedContract as Erc721Consumable; - - await token.mint(); - }) - - beforeEach(async function () { - snapshotId = await ethers.provider.send('evm_snapshot', []); - }); - - afterEach(async function () { - await ethers.provider.send('evm_revert', [snapshotId]); - }); - - it('should implement ERC165', async () => { - expect(await token.supportsInterface("0x953c8dfa")).to.be.true; - }) - - it('should successfully change consumer', async () => { - // when: - await token.changeConsumer(consumer.address, tokenID); - // then: - expect(await token.consumerOf(tokenID)).to.equal(consumer.address); - }); - - it('should emit event with args', async () => { - // when: - const tx = await token.changeConsumer(consumer.address, tokenID); - - // then: - await expect(tx) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, consumer.address, tokenID); - }); - - it('should successfully change consumer when caller is approved', async () => { - // given: - await token.approve(approved.address, tokenID); - // when: - const tx = await token.connect(approved).changeConsumer(consumer.address, tokenID); - - // then: - await expect(tx) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, consumer.address, tokenID); - // and: - expect(await token.consumerOf(tokenID)).to.equal(consumer.address); - }); - - it('should successfully change consumer when caller is operator', async () => { - // given: - await token.setApprovalForAll(operator.address, true); - // when: - const tx = await token.connect(operator).changeConsumer(consumer.address, tokenID); - - // then: - await expect(tx) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, consumer.address, tokenID); - // and: - expect(await token.consumerOf(tokenID)).to.equal(consumer.address); - }); - - it('should revert when caller is not owner, not approved', async () => { - const expectedRevertMessage = 'ERC721Consumable: changeConsumer caller is not owner nor approved'; - await expect(token.connect(other).changeConsumer(consumer.address, tokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should revert when caller is approved for the token', async () => { - // given: - await token.changeConsumer(consumer.address, tokenID); - // then: - const expectedRevertMessage = 'ERC721Consumable: changeConsumer caller is not owner nor approved'; - await expect(token.connect(consumer).changeConsumer(consumer.address, tokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should revert when tokenID is nonexistent', async () => { - const invalidTokenID = 2; - const expectedRevertMessage = 'ERC721: owner query for nonexistent token'; - await expect(token.changeConsumer(consumer.address, invalidTokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should revert when calling consumerOf with nonexistent tokenID', async () => { - const invalidTokenID = 2; - const expectedRevertMessage = 'ERC721Consumable: consumer query for nonexistent token'; - await expect(token.consumerOf(invalidTokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should clear consumer on transfer', async () => { - await token.changeConsumer(consumer.address, tokenID); - await expect(token.transferFrom(owner.address, other.address, tokenID)) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, ethers.constants.AddressZero, tokenID); - }) - - it('should emit ConsumerChanged on mint', async () => { - await expect(token.mint()) - .to.emit(token, 'ConsumerChanged') - .withArgs(ethers.constants.AddressZero, ethers.constants.AddressZero, tokenID + 1); - }) - - it('should not be able to transfer from consumer', async () => { - const expectedRevertMessage = 'ERC721: transfer caller is not owner nor approved'; - await token.changeConsumer(consumer.address, tokenID); - await expect(token.connect(consumer).transferFrom(owner.address, other.address, tokenID)) - .to.revertedWith(expectedRevertMessage) - }) -}); \ No newline at end of file diff --git a/assets/eip-4400/tsconfig.json b/assets/eip-4400/tsconfig.json deleted file mode 100644 index 57fc82e..0000000 --- a/assets/eip-4400/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "outDir": "dist", - "resolveJsonModule": true - }, - "exclude": [ - "contracts" - ], - "include": [ - "./scripts", - "./test" - ], - "files": [ - "./hardhat.config.ts" - ] -} diff --git a/assets/eip-4488/gas_and_calldata_sample.csv b/assets/eip-4488/gas_and_calldata_sample.csv deleted file mode 100644 index af49e30..0000000 --- a/assets/eip-4488/gas_and_calldata_sample.csv +++ /dev/null @@ -1,6871 +0,0 @@ -Gas, gas used, calldata bytes -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 -500000, 111711, 260 -464096, 311440, 2500 -800000, 323095, 932 -75865, 63221, 68 -34795, 34795, 68 -21000, 21000, 0 -21000, 21000, 0 -150000, 84032, 356 -21000, 21000, 0 -80815, 53530, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -105000, 63221, 68 -105000, 46109, 68 -105000, 46097, 68 -95000, 34989, 68 -64733, 54016, 68 -100000, 65058, 228 -338108, 249986, 260 -94813, 63209, 68 -30000, 21000, 0 -158666, 87719, 356 -199352, 98833, 324 -73711, 51547, 68 -120000, 47115, 68 -50000, 21000, 0 -400000, 37190, 68 -50000, 21000, 0 -90000, 34740, 68 -53615, 46622, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -69826, 46551, 68 -360915, 238860, 260 -420000, 21000, 0 -105000, 35065, 68 -105000, 34497, 68 -420000, 51883, 68 -105000, 35065, 68 -67798, 52153, 68 -1000000, 65625, 68 -1000000, 52344, 68 -105000, 21000, 0 -1000000, 64117, 68 -21000, 21000, 0 -21000, 21000, 0 -50719, 46109, 68 -46574, 46195, 68 -21000, 21000, 0 -21000, 21000, 0 -65000, 63209, 68 -186660, 144803, 260 -63000, 21000, 0 -74685, 47238, 68 -73639, 46366, 68 -90912, 60311, 68 -76676, 48897, 68 -21000, 21000, 0 -21000, 21000, 0 -66163, 65625, 68 -58500, 39457, 68 -73932, 51701, 68 -351400, 58670, 228 -21000, 21000, 0 -67798, 52153, 68 -45568, 35053, 68 -45568, 35053, 68 -67783, 52141, 68 -45568, 35053, 68 -45568, 35053, 68 -67798, 52153, 68 -45568, 35053, 68 -276714, 203765, 228 -60760, 60311, 68 -69163, 46109, 68 -84000, 48537, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 65613, 68 -250000, 51842, 68 -250000, 30324, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63197, 68 -21000, 21000, 0 -274546, 204205, 228 -199518, 141377, 260 -50000, 21000, 0 -50000, 36745, 0 -50000, 21000, 0 -200000, 52101, 68 -216034, 134250, 68 -220387, 139325, 68 -208084, 128950, 68 -146416, 97611, 228 -216034, 134250, 68 -72792, 60837, 68 -21000, 21000, 0 -250000, 65613, 68 -250000, 51618, 68 -250000, 63209, 68 -250000, 46109, 68 -21000, 21000, 0 -21000, 21000, 0 -250000, 63185, 68 -21000, 21000, 0 -21000, 21000, 0 -46458, 46458, 68 -72823, 48549, 68 -221617, 177894, 741 -21000, 21000, 0 -21000, 21000, 0 -250000, 30019, 68 -250000, 43725, 68 -250000, 35318, 68 -250000, 32349, 68 -250000, 29995, 68 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -21000, 21000, 0 -250000, 30007, 68 -250000, 29995, 68 -250000, 29801, 68 -216034, 134250, 68 -218777, 174205, 741 -184280, 136844, 1605 -591388, 581894, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -250000, 41309, 68 -397746, 271747, 260 -84000, 63197, 68 -21000, 21000, 0 -216407, 173130, 741 -1083221, 748810, 11140 -188263, 154461, 292 -69579, 46386, 68 -99226, 60813, 68 -252590, 185604, 260 -177663, 144507, 260 -21000, 21000, 0 -21000, 21000, 0 -89509, 59673, 68 -289917, 236617, 452 -21000, 21000, 0 -21000, 21000, 0 -328651, 243067, 2404 -186273, 131073, 580 -243811, 194718, 292 -151988, 116198, 260 -55904, 46587, 68 -222578, 182796, 292 -73188, 48525, 68 -69565, 46377, 68 -76373, 76373, 4 -21000, 21000, 0 -31091, 30597, 68 -31093, 31093, 164 -21000, 21000, 0 -21000, 21000, 0 -516883, 397602, 4 -49810, 49444, 68 -46458, 46458, 68 -81091, 81091, 36 -21000, 21000, 0 -94831, 63221, 68 -192939, 148081, 196 -53786, 48897, 68 -21000, 21000, 0 -305274, 240686, 1317 -384582, 254638, 260 -248867, 241097, 516 -21000, 21000, 0 -218864, 202318, 324 -46622, 46622, 68 -928568, 921613, 68 -46726, 46726, 68 -86416, 57623, 68 -183349, 127177, 228 -264834, 192696, 292 -21000, 21000, 0 -55970, 46642, 68 -403845, 267480, 260 -303605, 223801, 2404 -21000, 21000, 0 -142123, 41309, 68 -123188, 43725, 68 -21000, 21000, 0 -170000, 41297, 68 -170000, 41297, 68 -21000, 21000, 0 -93292, 61715, 68 -21000, 21000, 0 -142123, 41309, 68 -173205, 118856, 228 -346944, 259139, 2788 -185338, 128292, 580 -55932, 46610, 68 -325135, 320541, 36 -179888, 113786, 228 -21000, 21000, 0 -21000, 21000, 0 -77535, 46890, 68 -21000, 21000, 0 -197936, 140059, 260 -21000, 21000, 0 -278679, 227251, 292 -156000, 156000, 36 -53748, 53748, 68 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -21000, 21000, 0 -250000, 21000, 0 -250000, 21000, 0 -81493, 54329, 36 diff --git a/assets/eip-4519/ESP32_Firmware/main.cpp b/assets/eip-4519/ESP32_Firmware/main.cpp deleted file mode 100644 index 7e13258..0000000 --- a/assets/eip-4519/ESP32_Firmware/main.cpp +++ /dev/null @@ -1,709 +0,0 @@ -/*** - * This example of ESP32 (Pycom LoPy4) firmware was developed by Javier Arcenegui at "Instituto de - * Microelectrónica de Sevilla IMSE-CNM (Universidad de Sevilla-CSIC)" for the EIP4519. This firmware makes the device generate its own Ethereum account and save in the EEPROM the data required in the future to regenerate - * the same account. In the newest versions of ESP32 ("ESP32 S2" or - * "ESP32 S3") these data can be saved in a protected area. - * - * The Smart Contract is published on "0x6Dba58fF5AA2d8447C4460d2527033A81646Ae97". - * It was proved in the Ethereum Kovan testnet. - * - * This code was developed with the infuraIO extension for Visual Studio Code and the Arduino Framework. - * The alphawallet/Web3E@^1.22 library must be added in the configuration file (platformio.ini) - * - * Helper data are employed to regenerate the same Ethereum account based on CTR-DRBG PRNG. - * The helper data format is the following: - * - * ------------------------- Helper data Format ----------------------- - * |---------------|----------------|----------------|----|----|----| - * | key_ctr |Personalization | contex_counter | rc | el | ri | - * |---------------|----------------|----------------|----|----|----| - * 51 35 19 3 2 1 0 (Byte) - * - ***/ - -#include -#include -#include -#include -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/entropy.h" -#include -#include -#include "esp_wifi.h" -#include -#include -#include -#include -#include "Trezor/secp256k1.h" -#include "Trezor/ecdsa.h" -#include "Trezor/sha3.h" - -#define EEPROM_SIZE 52 //The size required by the 51 bytes of helper data and the byte to know if the token is registered on the blockchain -#define EIP4519CONTRACT "0x6Dba58fF5AA2d8447C4460d2527033A81646Ae97" //This is the reference for the EIP4519 Smart Non-Fungible Token on Kovan (IT MUST BE CHANGED) - -//These are the states of the EIP4519 -enum STATES{ - waitingForOwner = 0, - engagedWithOwner = 1, - waitingForUser = 2, - engagedWithUser = 3 -}; - -void setupWifi(); //This function is the same as the wifi example of the ESP32 Arduino Wifi -uint8_t HexTo4Bits(uint8_t Hex); //This function is used to change an hexadecimal defined in ASCII character to a uint8_t -STATES queryTokenStatus(); //This function is used to recover the information of the SmartNFT associated to this device -void hex2Char(unsigned char *inHex, unsigned char *charH, unsigned char *charL); //This function is needed to convert an uint8_t to two ASCII characters - -void sendEngage(); //This function sends the hash of the shared key to complete the transaction in Ethereum - -//Declaration of the variable to save the state of the SmartNFT -STATES tokenState; - -//Declaration of the variables for the private/public Keys and the Address associated - -unsigned char privateKey_ethAccount[32]; -unsigned char publicKey_ethAccount[64]; -unsigned char ethAddress[20]; - -//Declaration of the variable to save the helper data generated/recovered, which are employed to obtain the private Key of Ethereum account -uint8_t helperData[51]; - -//These variables are used to save the addresses in ASCII associated to the SmartNFT and the SmartNFT ID -unsigned char OwnerAddress[42]; -unsigned char UserAddress[42]; -unsigned char deviceAddress[43]; -uint32_t tokenID; - -//Wifi setup parameters: IT MUST BE CHANGED -int wificounter = 0; -const char *ssid = ""; -const char *password = "= 10) - { - printf("Restarting ...\n"); - ESP.restart(); //Targeting 8266 & ESP32. You may need to replace this - - } - - delay(10); - - printf("\n"); - printf("WiFi connected.\n"); - printf("IP address: %d.%d.%d.%d\n",WiFi.localIP()[0],WiFi.localIP()[1],WiFi.localIP()[2],WiFi.localIP()[3]); -} - -STATES queryTokenStatus(){ - Contract contract(&web3, EIP4519CONTRACT); - //Generate the JSON to check the status - deviceAddress[0] = '0'; - deviceAddress[1] = 'x'; - for(int i = 0 ; i < 20 ; i++){ - hex2Char(ðAddress[i], &deviceAddress[2*i+2], &deviceAddress[2*i+3]); - } - - String tokenAddress; - - printf("Query Status from token : "); - for(int i = 0 ; i < 40 ; i++){ - printf("%c",deviceAddress[i+2]); - tokenDevice[i+2] = deviceAddress[i+2]; - } - printf("\n"); - - - //Call the function to get SmartNFT information - String func = "getInfoTokenFromBCA(address)"; - string param = contract.SetupContractData(func.c_str(), &tokenDevice); - string result = contract.ViewCall(¶m); - - //When data from blockchain are received, the SmartNFT information is saved - printf("%s\n",result.c_str()); - int i = 0; - while(result[i] != 'x'){ - i++; - } - i++; - OwnerAddress[0] = UserAddress[0] = '0'; - OwnerAddress[1] = UserAddress[1] = 'x'; - //Owner address - for(int j =24;j<64;j++){ - OwnerAddress[j-22] = result[i+j]; - } - i +=64; - //User address - for(int j =24;j<64;j++){ - UserAddress[j-22] = result[i+j]; - } - - printf("\nOwner of token : 0x"); - for(int j = 0 ; j < 40 ; j++){ - printf("%c",OwnerAddress[j+2]); - } - printf("\nUser of token : 0x"); - for(int j = 0 ; j < 40 ; j++){ - printf("%c",UserAddress[j+2]); - } - i +=64; - - //tokenID - tokenID = HexTo4Bits(result[i+56])*268435456+HexTo4Bits(result[i+57])*16777216+HexTo4Bits(result[i+58])*1048576+HexTo4Bits(result[i+59])*65536+HexTo4Bits(result[i+60])*4096+HexTo4Bits(result[i+61])*256+HexTo4Bits(result[i+62])*16+HexTo4Bits(result[i+63]); - printf("\nToken ID = %d\n",tokenID); - - //SmartNFT state - switch ((result[i+63]) - { - case '1': - return engagedWithOwner; - break; - case '2': - return waitingForUser; - break; - case '3': - return engagedWithUser; - break; - default: - return waitingForOwner - break; - } -} - - -uint8_t HexTo4Bits(uint8_t Hex){ - if (Hex>='0'&&Hex<='9'){ - return (Hex-'0'); - }else if(Hex>='a'&&Hex<='f'){ - return Hex-'a'+10; - }else if(Hex>='A'&&Hex<='F'){ - return Hex-'A'+10; - } -} - -void hex2Char(unsigned char *inHex, unsigned char *charH, unsigned char *charL){ - uint8_t charIN[2]; - charIN[0] = (inHex[0] / 16); - charIN[1] = (inHex[0] % 16); - if (charIN[0] < 10){ - charH[0] = charIN[0] + '0'; - }else{ - charH[0] = charIN[0] + 'a' - 10; - } - if (charIN[1] < 10){ - charL[0] = charIN[1] + '0'; - }else{ - charL[0] = charIN[1] + 'a' - 10; - } -} - -void sendEngage(){ - Contract contract(&web3, EIP4519CONTRACT); - //Define the function to call the blockchain. This function depends on the SmartNFT state - string func; - if(tokenState == waitingForOwner){ - func = "ownerEngagement(uint256)"; - }else if(tokenState == waitingForUser){ - func = "userEngagement(uint256)"; - } - - //Generate the hash of the shared key - keccak_256(K_XD,32,hash_K_XD); - - //Put this hash as a uint256 variable - uint256_t hash_K_XD_uin256 = 0; - for(int i =0 ; i < 32 ; i++){ - hash_K_XD_uin256 += (uint256_t)hash_K_XD[i]<<8*(7-i); - } - - //Call the function - string param = contract.SetupContractData(func.c_str(), &hash_K_XD_uin256); - string result = contract.Call(¶m); - -} diff --git a/assets/eip-4519/ESP32_Firmware/readme.md b/assets/eip-4519/ESP32_Firmware/readme.md deleted file mode 100644 index 611e466..0000000 --- a/assets/eip-4519/ESP32_Firmware/readme.md +++ /dev/null @@ -1,16 +0,0 @@ -#EIP4519 Proof of Concept - Firmware -This firmware is designed for a device using an ESP32 as a smart asset associated with an EIP4519 SmartNFT. The device has two operation modes: registration mode and application mode. -##Registration mode -In this mode, the device generates 51 bytes with the TRNG of the ESP32 core. Those bytes are used for the initial values of a CTR-DRBG PRNG to generate the private key of the Ethereum account. Only the address of this account is shared. The UART port is needed for communications with this device. -The commands in this mode are: ->‘0’ – Check if the device is ready. ->‘1’ – Share the address of the account. ->‘2’ – Save the initial values of CTR-DRBG PRNG in an EEPROM and changes the operation mode. -##Application Mode -The device reads the EEPROM to obtain the initial values of the CTR-DRBG PRNG and recover the Ethereum account. The device connects to a WiFi station. With Infura, the device checks the state of its associated SmartNFT registered on an EIP4519 Smart Contract and also checks if the device must be engaged with the owner or the user. The UART port is needed for communications with this device. -The commands in this mode are: ->'Z'+OWNER/USER_ADDRESS – The device checks if the address must be authenticated and generates a nonce. ->'Y'+SIGN_D+'#'+NONCE_D – The device checks the signature, signs NONCE_D, and sends the signature. ->'Y'+SIGNED_PK+'#'+PK – The device checks the signature, generates the shared key, and sends the transaction to the EIP4519 Smart Contract. ->'C' – The EEPROM is cleared, only for debug process. ->'R' – The device is restarted to refresh the SmartNFT state, only for debug process. diff --git a/assets/eip-4519/PoC_SmartNFT/ERC721_interface.sol b/assets/eip-4519/PoC_SmartNFT/ERC721_interface.sol deleted file mode 100644 index 1fef13e..0000000 --- a/assets/eip-4519/PoC_SmartNFT/ERC721_interface.sol +++ /dev/null @@ -1,126 +0,0 @@ -///SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; -/// @title ERC-721 Non-Fungible Token Standard -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md -/// Note: the ERC-165 identifier for this interface is 0x80ac58cd -interface ERC721 /* is ERC165 */ { - /// @dev This emits when ownership of any NFT changes by any mechanism. - /// This event emits when NFTs are created (`from` == 0) and destroyed - /// (`to` == 0). Exception: during contract creation, any number of NFTs - /// may be created and assigned without emitting Transfer. At the time of - /// any transfer, the approved address for that NFT (if any) is reset to none. - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - /// @dev This emits when the approved address for an NFT is changed or - /// reaffirmed. The zero address indicates there is no approved address. - /// When a Transfer event emits, this also indicates that the approved - /// address for that NFT (if any) is reset to none. - event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); - - /// @dev This emits when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - - /// @notice Count all NFTs assigned to an owner - /// @dev NFTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param _owner An address for whom to query the balance - /// @return The number of NFTs owned by `_owner`, possibly zero - function balanceOf(address _owner) external view returns (uint256); - - /// @notice Find the owner of an NFT - /// @dev NFTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param _tokenId The identifier for an NFT - /// @return The address of the owner of the NFT - function ownerOf(uint256 _tokenId) external view returns (address); - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. When transfer is complete, this function - /// checks if `_to` is a smart contract (code size > 0). If so, it calls - /// `onERC721Received` on `_to` and throws if the return value is not - /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - /// @param data Additional data with no specified format, sent in call to `_to` - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external payable; - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev This works identically to the other function with an extra data parameter, - /// except this function just sets data to "" - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE - /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE - /// THEY MAY BE PERMANENTLY LOST - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Set or reaffirm the approved address for an NFT - /// @dev The zero address indicates there is no approved address. - /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized - /// operator of the current owner. - /// @param _approved The new approved NFT controller - /// @param _tokenId The NFT to approve - function approve(address _approved, uint256 _tokenId) external payable; - - /// @notice Enable or disable approval for a third party ("operator") to manage - /// all of `msg.sender`'s assets. - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// multiple operators per owner. - /// @param _operator Address to add to the set of authorized operators. - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAll(address _operator, bool _approved) external; - - /// @notice Get the approved address for a single NFT - /// @dev Throws if `_tokenId` is not a valid NFT - /// @param _tokenId The NFT to find the approved address for - /// @return The approved address for this NFT, or the zero address if there is none - function getApproved(uint256 _tokenId) external view returns (address); - - - /// @notice Query if an address is an authorized operator for another address - /// @param _owner The address that owns the NFTs - /// @param _operator The address that acts on behalf of the owner - /// @return True if `_operator` is an approved operator for `_owner`, false otherwise - function isApprovedForAll(address _owner, address _operator) external view returns (bool); -} - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} - -interface ERC721TokenReceiver { - /// @notice Handle the receipt of an NFT - /// @dev The ERC721 smart contract calls this function on the - /// recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return - /// of other than the magic value MUST result in the transaction being reverted. - /// @notice The contract address is always the message sender. - /// @param _operator The address which called `safeTransferFrom` function - /// @param _from The address which previously owned the token - /// @param _tokenId The NFT identifier which is being transferred - /// @param _data Additional data with no specified format - /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - /// unless throwing - function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes memory _data) external returns(bytes4); -} diff --git a/assets/eip-4519/PoC_SmartNFT/PoC_SmartNFT.sol b/assets/eip-4519/PoC_SmartNFT/PoC_SmartNFT.sol deleted file mode 100644 index 2681dbc..0000000 --- a/assets/eip-4519/PoC_SmartNFT/PoC_SmartNFT.sol +++ /dev/null @@ -1,272 +0,0 @@ -///SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./smartNFT_interface.sol"; -import "./ERC721_interface.sol"; - -contract smartNFT_SC is ERC721,smartNFT{ - enum States { waitingForOwner, engagedWithOwner, waitingForUser, engagedWithUser } - - address manufacturer; //Address of manufacturer and owner of Smart Contract - uint256 tokenCounter; //To give a genuine tokenID based on the number of tokens created - mapping(uint256 => address) ownerOfSD; //To khow who is the owner of a specific owner - mapping(address => uint256) tokenIDOfBCA; //To khow which is the tokenID associated to a secure device from the address - mapping(address => uint256) ownerBalance; //To know how many tokens an owner has - mapping(address => uint256) userBalance; //To know how many tokens a user can use - - struct Token_Struct{ - address approved; //Indicate who can transfer this token, 0 if no one - address SD; //Indicate the address of the secure device associated to this token - address user; //Indicate who can use this secure device - States state; //If blocked (false) then token should be verified by a new user or a new owner - uint256 hashK_OD; //Hash of the Key shared between owner and device - uint256 hashK_UD; //Hash of the Key shared between user and device - uint256 dataEngagement; //Public Key to create K_OD or K_UD depending on token state - uint256 timestamp; //Last time that device updated its proof of live - uint256 timeout; //timeout to verify a device error - } - - Token_Struct[] Secure_Token; - - constructor() { - manufacturer = msg.sender; - tokenCounter = 1; - Secure_Token.push(Token_Struct(address(0), address(0), address(0), States.waitingForOwner,0,0,0,0,0)); - - } - - function createToken(address _addressSD, address _addressOwner) public virtual override returns (uint256){ - //Check if the sender of message is the manufacturer - require(manufacturer == msg.sender); - //Check if the Blockchain Account of the secure device is in the SmartContract - if(tokenFromBCA(_addressSD)==0){ - //Create a new token - Secure_Token.push(Token_Struct(address(0), _addressSD, address(0), States.waitingForOwner,0,0,0,block.timestamp,86400)); - //Assigning a new tokenId - uint256 _tokenId = tokenCounter ++; - tokenIDOfBCA[_addressSD] = _tokenId; - //Assigning the owner - ownerOfSD[_tokenId] = _addressOwner; - ownerBalance[_addressOwner]++; - //Return tokenId obtained - return(_tokenId); - }else{ - //If the BCA already exists then return the _tokenId - return(tokenFromBCA(_addressSD)); - } - } - - function setUser(uint256 _tokenId, address _addressUser) public virtual override{ - //Check the sender and the token state - require((ownerOfSD[_tokenId] == msg.sender) && (Secure_Token[_tokenId].state >= States.engagedWithOwner)); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - //Only to avoid overflow, for example, in address 0. - if(userBalance[Secure_Token[_tokenId].user]>0){ - //Update the balance of tokens assigned to the old user - userBalance[Secure_Token[_tokenId].user]--; - } - //Update the balance of tokens assigned to the new user - userBalance[_addressUser]++; - //Assign the new user to the token - Secure_Token[_tokenId].user = _addressUser; - //Update the state of the token - Secure_Token[_tokenId].state = States.waitingForUser; - //Erase old key exchange data between device with old user assigned - Secure_Token[_tokenId].dataEngagement =0; - Secure_Token[_tokenId].hashK_UD = 0; - emit UserAssigned(_tokenId,_addressUser); - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function startOwnerEngagement(uint256 _tokenId, uint256 _dataEngagement, uint256 _hashK_O) public virtual override{ - //Check if sender is the Owner of token and the State of token - require(ownerOfSD[_tokenId] == msg.sender); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - Secure_Token[_tokenId].dataEngagement = _dataEngagement; - Secure_Token[_tokenId].hashK_OD = _hashK_O; - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function ownerEngagement(uint256 _hashK_D) public virtual override{ - uint256 _tokenId = tokenFromBCA(msg.sender); - //Check if public key owner-device exists from tokenID of BCA sender - require(Secure_Token[_tokenId].dataEngagement != 0); - require (Secure_Token[_tokenId].hashK_OD == _hashK_D); - require (Secure_Token[_tokenId].state == States.waitingForOwner); - //Erase PK_Owner-Device and update timestamp - Secure_Token[_tokenId].dataEngagement = 0; - Secure_Token[_tokenId].timestamp = block.timestamp; - //Update the state of token - Secure_Token[_tokenId].state = States.engagedWithOwner; - //Send a notification to owner and device - emit OwnerEngaged(_tokenId); - } - - function startUserEngagement(uint256 _tokenId, uint256 _dataEngagement, uint256 _hashK_U) public virtual override{ - //Check the sender and the state of token - require(Secure_Token[_tokenId].user == msg.sender); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - Secure_Token[_tokenId].dataEngagement = _dataEngagement; - Secure_Token[_tokenId].hashK_UD = _hashK_U; - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function userEngagement(uint256 _hashK_D) public virtual override{ - uint256 _tokenId = tokenFromBCA(msg.sender); - //Check if public key user-device exists from tokenID of BCA sender - require(Secure_Token[_tokenId].dataEngagement != 0); - require (Secure_Token[_tokenId].hashK_UD == _hashK_D); - require (Secure_Token[_tokenId].state == States.waitingForUser); - //Erase PK_User-Device and update timestamp - Secure_Token[_tokenId].dataEngagement = 0; - Secure_Token[_tokenId].timestamp = block.timestamp; - //Update the state of token - Secure_Token[_tokenId].state = States.engagedWithUser; - //Send a notification to user and device - emit UserEngaged(_tokenId); - } - - - function tokenFromBCA(address _addressSD) public virtual view override returns (uint256){ - return(tokenIDOfBCA[_addressSD]); - } - - function ownerOfFromBCA(address _addressSD) public virtual view override returns (address){ - return(ownerOfSD[tokenIDOfBCA[_addressSD]]); - } - - function userOf(uint256 _tokenId) public virtual view override returns (address){ - return(Secure_Token[_tokenId].user); - } - - function userOfFromBCA(address _addressSD) public virtual override view returns (address){ - return(Secure_Token[tokenIDOfBCA[_addressSD]].user); - } - - function userBalanceOf(address _addressUser) public virtual override view returns (uint256){ - return(userBalance[_addressUser]); - } - - function userBalanceOfAnOwner(address _addressUser, address _addressOwner) public virtual override view returns (uint256){ - //TODO - } - - function getInfoToken(uint256 _tokenId) public view returns ( address _BCA_OWNER, - address _BCA_USER, - address _BCA_SD, - uint8 _state){ - _BCA_OWNER = ownerOfSD[_tokenId]; - _BCA_USER = Secure_Token[_tokenId].user; - _BCA_SD = Secure_Token[_tokenId].SD; - if(Secure_Token[_tokenId].state == States.waitingForOwner){ - _state = 0; - }else if(Secure_Token[_tokenId].state == States.engagedWithOwner){ - _state = 1; - }else if(Secure_Token[_tokenId].state == States.waitingForUser){ - _state = 2; - }else { - _state = 3; - } - } - - function getInfoTokenFromBCA(address _addressSD) public view returns ( address _BCA_OWNER, - address _BCA_USER, - uint256 _tokenId, - uint8 _state){ - _tokenId = tokenIDOfBCA[_addressSD]; - _BCA_OWNER = ownerOfSD[_tokenId]; - _BCA_USER = Secure_Token[_tokenId].user; - if(Secure_Token[_tokenId].state == States.waitingForOwner){ - _state = 0; - }else if(Secure_Token[_tokenId].state == States.engagedWithOwner){ - _state = 1; - }else if(Secure_Token[_tokenId].state == States.waitingForUser){ - _state = 2; - }else { - _state = 3; - } - } - - function balanceOf(address _owner) public virtual override view returns (uint256){ - return(ownerBalance[_owner]); - } - - function ownerOf(uint256 _tokenId) public virtual override view returns (address){ - return(ownerOfSD[_tokenId]); - } - - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public virtual override payable{ - - } - - function safeTransferFrom(address _from, address _to, uint256 _tokenId) public virtual override payable{ - transferFrom(_from, _to, _tokenId); - } - - function transferFrom(address _from, address _to, uint256 _tokenId) public virtual override payable{ - require((ownerOfSD[_tokenId] == msg.sender)||(Secure_Token[_tokenId].approved == msg.sender)); - require(ownerOfSD[_tokenId] == _from); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - ownerOfSD[_tokenId] = _to; - ownerBalance[_from]--; - ownerBalance[_to]++; - //Secure_Token[_tokenId].approved = address(0); - Secure_Token[_tokenId].user = address(0); - Secure_Token[_tokenId].state = States.waitingForOwner; - //Erase old key exchange data between device with old Owner - Secure_Token[_tokenId].dataEngagement = 0; - Secure_Token[_tokenId].hashK_UD = 0; - Secure_Token[_tokenId].hashK_OD = 0; - emit Transfer(_from,_to,_tokenId); - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function approve(address _approved, uint256 _tokenId) public virtual override payable{ - - } - - function setApprovalForAll(address _operator, bool _approved) public virtual override{ - - } - - function getApproved(uint256 _tokenId) public virtual override view returns (address){ - - } - - function isApprovedForAll(address _owner, address _operator) public virtual override view returns (bool){ - - } - - function checkTimeout(uint256 _tokenId) public virtual override returns (bool){ - require(ownerOfSD[_tokenId] == msg.sender); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - return true; - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - return false; - } - } - - function updateTimestamp() public virtual override{ - Secure_Token[tokenFromBCA(msg.sender)].timestamp = block.timestamp; - } - - function setTimeout(uint256 _tokenId, uint256 _timeout) public virtual override{ - require(ownerOfSD[_tokenId] == msg.sender); - Secure_Token[_tokenId].timeout = _timeout; - } -} diff --git a/assets/eip-4519/PoC_SmartNFT/README.md b/assets/eip-4519/PoC_SmartNFT/README.md deleted file mode 100644 index cbb2773..0000000 --- a/assets/eip-4519/PoC_SmartNFT/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Proof of concept of an implementation of an Smart Non Fungible Token -This proof of concept is launch in the Ethereum Kovan Testnet with the address 0x7eB5A03E7ED70ABf70fee48965D0411d37F335aC. -Use the proposal Non Fungible Token binding assets with SmartNFT and define the user management of the assets. diff --git a/assets/eip-4519/PoC_SmartNFT/SmartNFT_interface.sol b/assets/eip-4519/PoC_SmartNFT/SmartNFT_interface.sol deleted file mode 100644 index 6b21203..0000000 --- a/assets/eip-4519/PoC_SmartNFT/SmartNFT_interface.sol +++ /dev/null @@ -1,83 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/// @title smartNFT: Hardblock - ERC-721 Non-Fungible Token Standard-based -interface smartNFT{ - /// @dev This emits when an user of a NFT changes - /// This event emits when the user of a token is assigned - /// (`_addressUser` == 0) when a user is unassigned - event UserAssigned(uint256 indexed tokenID, address indexed _addressUser); - - /// @dev This emits when an user of a NFT verifies a device - /// This event emits when the user of a device finishes the assignment process - event UserEngaged(uint256 indexed tokenID); - - /// @dev This emits when an owner of a NFT verifies a device - /// This event emits when the owner of a device finishes the transfer process - event OwnerEngaged(uint256 indexed tokenID); - - //TODO: Describe new functions and events - event TimeoutAlarm(uint256 indexed tokenID); - - /// @notice This function defines how the smart device is bound to a new token - /// @dev Only the manufacturer of the smart device account can create a token and will be the first owner of the token - /// The initial state of the token is "waitingForOwner" until verified by the new owner - /// @param _addressSD An address generated by the smart device. Only de smart device can generate this account. _addressOwner is the first owner of the Smart Device - /// @return The tokenID of the token bound to the smart device - function createToken(address _addressSD, address _addressOwner) external returns (uint256); - - /// @notice This function defines the transference of use of a smart device - /// @dev Only the owner of the token account can transfer a token provided that the state of the token is "engagedWithOwner","waitingForUser" or "engagedWithUser". - /// The state of the token must change to "waitingForUser" and the parameter addressUser of the token defined by _tokenID must change to _addressUser - /// @param _tokenId The tokenID of the smart device - /// @param _addressUser The address of the new user - function setUser(uint256 _tokenId, address _addressUser) external; - - //TODO: Describe new functions and events - - function startOwnerEngagement(uint256 _tokenId, uint256 _dataEngage, uint256 _hashK_O) external; - function ownerEngagement(uint256 _hashK_D) external; - function startUserEngagement(uint256 _tokenId, uint256 _dataEngage, uint256 _hashK_U) external; - function userEngagement(uint256 _hashK_D) external; - function checkTimeout(uint256 _tokenId) external returns (bool); - function setTimeout(uint256 _tokenId, uint256 _timeout) external; - function updateTimestamp() external; - - /// @notice This function let us obtain the tokenID from an address of smart device - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressSD The address to obtain its token ID - /// @return The token ID of the token bound to _addressSD - function tokenFromBCA(address _addressSD) external view returns (uint256); - - /// @notice This function let us know who is the owner of the token from the address of the smart device - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressSD The address to obtain its owner - /// @return The owner of the token bound to _addressSD - function ownerOfFromBCA(address _addressSD) external view returns (address); - - /// @notice This function let us know who is the user of the token from the tokenId - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _tokenId of the token to obtain its user - /// @return The user of the token with _tokenId - function userOf(uint256 _tokenId) external view returns (address); - - /// @notice This function let us know who is the user of the token from the address of the smart device - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressSD The address to obtain its user. - /// @return The user of the token bound to _addressSD. - function userOfFromBCA(address _addressSD) external view returns (address); - - /// @notice This function let us know how many tokens an user has - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressUser The address of the user to know the number of tokens - /// @return The number of tokens of the user - function userBalanceOf(address _addressUser) external view returns (uint256); - - /// @notice This function let us know how many tokens of an owner an user has - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressUser The address of the user to know the number of tokens - /// @param _addressOwner The address of the owner of the tokens - /// @return The number of tokens of an owner that an user can use - function userBalanceOfAnOwner(address _addressUser, address _addressOwner) external view returns (uint256); -} diff --git a/assets/eip-4519/images/Figure1.jpg b/assets/eip-4519/images/Figure1.jpg deleted file mode 100644 index ac70ae6..0000000 Binary files a/assets/eip-4519/images/Figure1.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure2.jpg b/assets/eip-4519/images/Figure2.jpg deleted file mode 100644 index aa99370..0000000 Binary files a/assets/eip-4519/images/Figure2.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure3.jpg b/assets/eip-4519/images/Figure3.jpg deleted file mode 100644 index dd0e0ef..0000000 Binary files a/assets/eip-4519/images/Figure3.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure4.jpg b/assets/eip-4519/images/Figure4.jpg deleted file mode 100644 index 62f1f81..0000000 Binary files a/assets/eip-4519/images/Figure4.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure5.jpg b/assets/eip-4519/images/Figure5.jpg deleted file mode 100644 index 1f8a170..0000000 Binary files a/assets/eip-4519/images/Figure5.jpg and /dev/null differ diff --git a/assets/eip-4519/sensors-21-03119.pdf b/assets/eip-4519/sensors-21-03119.pdf deleted file mode 100644 index 1c8f8db..0000000 Binary files a/assets/eip-4519/sensors-21-03119.pdf and /dev/null differ diff --git a/assets/eip-4671/ERC4671.sol b/assets/eip-4671/ERC4671.sol deleted file mode 100644 index 5343d9e..0000000 --- a/assets/eip-4671/ERC4671.sol +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -import "./IERC4671.sol"; -import "./IERC4671Metadata.sol"; -import "./IERC4671Enumerable.sol"; - -abstract contract ERC4671 is IERC4671, IERC4671Metadata, IERC4671Enumerable, ERC165 { - // Token data - struct Token { - address issuer; - address owner; - bool valid; - } - - // Mapping from tokenId to token - mapping(uint256 => Token) private _tokens; - - // Mapping from owner to token ids - mapping(address => uint256[]) private _indexedTokenIds; - - // Mapping from token id to index - mapping(address => mapping(uint256 => uint256)) private _tokenIdIndex; - - // Mapping from owner to number of valid tokens - mapping(address => uint256) private _numberOfValidTokens; - - // Token name - string private _name; - - // Token symbol - string private _symbol; - - // Total number of tokens emitted - uint256 private _emittedCount; - - // Total number of token holders - uint256 private _holdersCount; - - // Contract creator - address private _creator; - - constructor (string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - _creator = msg.sender; - } - - /// @notice Count all tokens assigned to an owner - /// @param owner Address for whom to query the balance - /// @return Number of tokens owned by `owner` - function balanceOf(address owner) public view virtual override returns (uint256) { - return _indexedTokenIds[owner].length; - } - - /// @notice Get owner of a token - /// @param tokenId Identifier of the token - /// @return Address of the owner of `tokenId` - function ownerOf(uint256 tokenId) public view virtual override returns (address) { - return _getTokenOrRevert(tokenId).owner; - } - - /// @notice Check if a token hasn't been revoked - /// @param tokenId Identifier of the token - /// @return True if the token is valid, false otherwise - function isValid(uint256 tokenId) public view virtual override returns (bool) { - return _getTokenOrRevert(tokenId).valid; - } - - /// @notice Check if an address owns a valid token in the contract - /// @param owner Address for whom to check the ownership - /// @return True if `owner` has a valid token, false otherwise - function hasValid(address owner) public view virtual override returns (bool) { - return _numberOfValidTokens[owner] > 0; - } - - /// @return Descriptive name of the tokens in this contract - function name() public view virtual override returns (string memory) { - return _name; - } - - /// @return An abbreviated name of the tokens in this contract - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /// @notice URI to query to get the token's metadata - /// @param tokenId Identifier of the token - /// @return URI for the token - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - _getTokenOrRevert(tokenId); - bytes memory baseURI = bytes(_baseURI()); - if (baseURI.length > 0) { - return string(abi.encodePacked( - baseURI, - Strings.toHexString(tokenId, 32) - )); - } - return ""; - } - - /// @return emittedCount Number of tokens emitted - function emittedCount() public view override returns (uint256) { - return _emittedCount; - } - - /// @return holdersCount Number of token holders - function holdersCount() public view override returns (uint256) { - return _holdersCount; - } - - /// @notice Get the tokenId of a token using its position in the owner's list - /// @param owner Address for whom to get the token - /// @param index Index of the token - /// @return tokenId of the token - function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { - uint256[] storage ids = _indexedTokenIds[owner]; - require(index < ids.length, "Token does not exist"); - return ids[index]; - } - - /// @notice Get a tokenId by it's index, where 0 <= index < total() - /// @param index Index of the token - /// @return tokenId of the token - function tokenByIndex(uint256 index) public view virtual override returns (uint256) { - return index; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return - interfaceId == type(IERC4671).interfaceId || - interfaceId == type(IERC4671Metadata).interfaceId || - interfaceId == type(IERC4671Enumerable).interfaceId || - super.supportsInterface(interfaceId); - } - - /// @notice Prefix for all calls to tokenURI - /// @return Common base URI for all token - function _baseURI() internal pure virtual returns (string memory) { - return ""; - } - - /// @notice Mark the token as revoked - /// @param tokenId Identifier of the token - function _revoke(uint256 tokenId) internal virtual { - Token storage token = _getTokenOrRevert(tokenId); - require(token.valid, "Token is already invalid"); - token.valid = false; - assert(_numberOfValidTokens[token.owner] > 0); - _numberOfValidTokens[token.owner] -= 1; - emit Revoked(token.owner, tokenId); - } - - /// @notice Mint a new token - /// @param owner Address for whom to assign the token - /// @return tokenId Identifier of the minted token - function _mint(address owner) internal virtual returns (uint256 tokenId) { - tokenId = _emittedCount; - _mintUnsafe(owner, tokenId, true); - emit Minted(owner, tokenId); - _emittedCount += 1; - } - - /// @notice Mint a given tokenId - /// @param owner Address for whom to assign the token - /// @param tokenId Token identifier to assign to the owner - /// @param valid Boolean to assert of the validity of the token - function _mintUnsafe(address owner, uint256 tokenId, bool valid) internal { - require(_tokens[tokenId].owner == address(0), "Cannot mint an assigned token"); - if (_indexedTokenIds[owner].length == 0) { - _holdersCount += 1; - } - _tokens[tokenId] = Token(msg.sender, owner, valid); - _tokenIdIndex[owner][tokenId] = _indexedTokenIds[owner].length; - _indexedTokenIds[owner].push(tokenId); - if (valid) { - _numberOfValidTokens[owner] += 1; - } - } - - /// @return True if the caller is the contract's creator, false otherwise - function _isCreator() internal view virtual returns (bool) { - return msg.sender == _creator; - } - - /// @notice Retrieve a token or revert if it does not exist - /// @param tokenId Identifier of the token - /// @return The Token struct - function _getTokenOrRevert(uint256 tokenId) internal view virtual returns (Token storage) { - Token storage token = _tokens[tokenId]; - require(token.owner != address(0), "Token does not exist"); - return token; - } - - /// @notice Remove a token - /// @param tokenId Token identifier to remove - function _removeToken(uint256 tokenId) internal virtual { - Token storage token = _getTokenOrRevert(tokenId); - _removeFromUnorderedArray(_indexedTokenIds[token.owner], _tokenIdIndex[token.owner][tokenId]); - if (_indexedTokenIds[token.owner].length == 0) { - assert(_holdersCount > 0); - _holdersCount -= 1; - } - if (token.valid) { - assert(_numberOfValidTokens[token.owner] > 0); - _numberOfValidTokens[token.owner] -= 1; - } - delete _tokens[tokenId]; - } - - /// @notice Removes an entry in an array by its index - /// @param array Array for which to remove the entry - /// @param index Index of the entry to remove - function _removeFromUnorderedArray(uint256[] storage array, uint256 index) internal { - require(index < array.length, "Trying to delete out of bound index"); - if (index != array.length - 1) { - array[index] = array[array.length - 1]; - } - array.pop(); - } -} diff --git a/assets/eip-4671/ERC4671Consensus.sol b/assets/eip-4671/ERC4671Consensus.sol deleted file mode 100644 index bc3f1fc..0000000 --- a/assets/eip-4671/ERC4671Consensus.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -import "./ERC4671.sol"; -import "./IERC4671Consensus.sol"; - -contract ERC4671Consensus is ERC4671, IERC4671Consensus { - // Consensus voters addresses - mapping(address => bool) private _voters; - address[] private _votersArray; - - // Mapping from voter to mint approvals - mapping(address => mapping(address => bool)) private _mintApprovals; - - // Mapping from owner to approval counts - mapping(address => uint256) private _mintApprovalCounts; - - // Mapping from voter to revoke approvals - mapping(address => mapping(uint256 => bool)) private _revokeApprovals; - - // Mapping from tokenId to revoke counts - mapping(uint256 => uint256) private _revokeApprovalCounts; - - constructor (string memory name_, string memory symbol_, address[] memory voters_) ERC4671(name_, symbol_) { - _votersArray = voters_; - for (uint256 i=0; i mapping(address => bool)) _allowed; - - /// @notice Grant one-time minting right to `operator` for `owner` - /// An allowed operator can call the function to transfer rights. - /// @param operator Address allowed to mint a token - /// @param owner Address for whom `operator` is allowed to mint a token - function delegate(address operator, address owner) public virtual override { - _delegateAsDelegateOrCreator(operator, owner, _isCreator()); - } - - /// @notice Grant one-time minting right to a list of `operators` for a corresponding list of `owners` - /// An allowed operator can call the function to transfer rights. - /// @param operators Addresses allowed to mint a token - /// @param owners Addresses for whom `operators` are allowed to mint a token - function delegateBatch(address[] memory operators, address[] memory owners) public virtual override { - require(operators.length == owners.length, "operators and owners must have the same length"); - bool isCreator = _isCreator(); - for (uint i=0; i address[]) private _records; - - // Mapping from owner to IERC4671Enumerable contract index - mapping(address => mapping(address => uint256)) _indices; - - /// @notice Add a IERC4671Enumerable contract address to the caller's record - /// @param token Address of the IERC4671Enumerable contract to add - function add(address token) public virtual override { - address[] storage contracts = _records[msg.sender]; - _indices[msg.sender][token] = contracts.length; - contracts.push(token); - emit Added(msg.sender, token); - } - - /// @notice Remove a IERC4671Enumerable contract from the caller's record - /// @param token Address of the IERC4671Enumerable contract to remove - function remove(address token) public virtual override { - uint256 index = _indexOfTokenOrRevert(msg.sender, token); - address[] storage contracts = _records[msg.sender]; - if (index == contracts.length - 1) { - _indices[msg.sender][token] = 0; - } else { - _indices[msg.sender][contracts[contracts.length - 1]] = index; - } - contracts[index] = contracts[contracts.length - 1]; - contracts.pop(); - emit Removed(msg.sender, token); - } - - /// @notice Get all the IERC4671Enumerable contracts for a given owner - /// @param owner Address for which to retrieve the IERC4671Enumerable contracts - function get(address owner) public view virtual override returns (address[] memory) { - return _records[owner]; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return - interfaceId == type(IERC4671Store).interfaceId || - super.supportsInterface(interfaceId); - } - - function _indexOfTokenOrRevert(address owner, address token) private view returns (uint256) { - uint256 index = _indices[owner][token]; - require(index > 0 || _records[owner].length > 0, "Address not found"); - return index; - } -} diff --git a/assets/eip-4671/IERC4671.sol b/assets/eip-4671/IERC4671.sol deleted file mode 100644 index 3b97b9d..0000000 --- a/assets/eip-4671/IERC4671.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IERC4671 is IERC165 { - /// Event emitted when a token `tokenId` is minted for `owner` - event Minted(address owner, uint256 tokenId); - - /// Event emitted when token `tokenId` of `owner` is revoked - event Revoked(address owner, uint256 tokenId); - - /// @notice Count all tokens assigned to an owner - /// @param owner Address for whom to query the balance - /// @return Number of tokens owned by `owner` - function balanceOf(address owner) external view returns (uint256); - - /// @notice Get owner of a token - /// @param tokenId Identifier of the token - /// @return Address of the owner of `tokenId` - function ownerOf(uint256 tokenId) external view returns (address); - - /// @notice Check if a token hasn't been revoked - /// @param tokenId Identifier of the token - /// @return True if the token is valid, false otherwise - function isValid(uint256 tokenId) external view returns (bool); - - /// @notice Check if an address owns a valid token in the contract - /// @param owner Address for whom to check the ownership - /// @return True if `owner` has a valid token, false otherwise - function hasValid(address owner) external view returns (bool); -} diff --git a/assets/eip-4671/IERC4671Consensus.sol b/assets/eip-4671/IERC4671Consensus.sol deleted file mode 100644 index 81e8663..0000000 --- a/assets/eip-4671/IERC4671Consensus.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Consensus is IERC4671 { - /// @notice Get voters addresses for this consensus contract - /// @return Addresses of the voters - function voters() external view returns (address[] memory); - - /// @notice Cast a vote to mint a token for a specific address - /// @param owner Address for whom to mint the token - function approveMint(address owner) external; - - /// @notice Cast a vote to revoke a specific token - /// @param tokenId Identifier of the token to revoke - function approveRevoke(uint256 tokenId) external; -} diff --git a/assets/eip-4671/IERC4671Delegate.sol b/assets/eip-4671/IERC4671Delegate.sol deleted file mode 100644 index cef2c68..0000000 --- a/assets/eip-4671/IERC4671Delegate.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Delegate is IERC4671 { - /// @notice Grant one-time minting right to `operator` for `owner` - /// An allowed operator can call the function to transfer rights. - /// @param operator Address allowed to mint a token - /// @param owner Address for whom `operator` is allowed to mint a token - function delegate(address operator, address owner) external; - - /// @notice Grant one-time minting right to a list of `operators` for a corresponding list of `owners` - /// An allowed operator can call the function to transfer rights. - /// @param operators Addresses allowed to mint - /// @param owners Addresses for whom `operators` are allowed to mint a token - function delegateBatch(address[] memory operators, address[] memory owners) external; - - /// @notice Mint a token. Caller must have the right to mint for the owner. - /// @param owner Address for whom the token is minted - function mint(address owner) external; - - /// @notice Mint tokens to multiple addresses. Caller must have the right to mint for all owners. - /// @param owners Addresses for whom the tokens are minted - function mintBatch(address[] memory owners) external; - - /// @notice Get the issuer of a token - /// @param tokenId Identifier of the token - /// @return Address who minted `tokenId` - function issuerOf(uint256 tokenId) external view returns (address); -} diff --git a/assets/eip-4671/IERC4671Enumerable.sol b/assets/eip-4671/IERC4671Enumerable.sol deleted file mode 100644 index 99f0eb3..0000000 --- a/assets/eip-4671/IERC4671Enumerable.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Enumerable is IERC4671 { - /// @return emittedCount Number of tokens emitted - function emittedCount() external view returns (uint256); - - /// @return holdersCount Number of token holders - function holdersCount() external view returns (uint256); - - /// @notice Get the tokenId of a token using its position in the owner's list - /// @param owner Address for whom to get the token - /// @param index Index of the token - /// @return tokenId of the token - function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256); - - /// @notice Get a tokenId by it's index, where 0 <= index < total() - /// @param index Index of the token - /// @return tokenId of the token - function tokenByIndex(uint256 index) external view returns (uint256); -} diff --git a/assets/eip-4671/IERC4671Metadata.sol b/assets/eip-4671/IERC4671Metadata.sol deleted file mode 100644 index adc2fa5..0000000 --- a/assets/eip-4671/IERC4671Metadata.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Metadata is IERC4671 { - /// @return Descriptive name of the tokens in this contract - function name() external view returns (string memory); - - /// @return An abbreviated name of the tokens in this contract - function symbol() external view returns (string memory); - - /// @notice URI to query to get the token's metadata - /// @param tokenId Identifier of the token - /// @return URI for the token - function tokenURI(uint256 tokenId) external view returns (string memory); -} diff --git a/assets/eip-4671/IERC4671Pull.sol b/assets/eip-4671/IERC4671Pull.sol deleted file mode 100644 index c719273..0000000 --- a/assets/eip-4671/IERC4671Pull.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Pull is IERC4671 { - /// @notice Pull a token from the owner wallet to the caller's wallet - /// @param tokenId Identifier of the token to transfer - /// @param owner Address that owns tokenId - /// @param signature Signed data (tokenId, owner, recipient) by the owner of the token - function pull(uint256 tokenId, address owner, bytes memory signature) external; -} diff --git a/assets/eip-4671/IERC4671Store.sol b/assets/eip-4671/IERC4671Store.sol deleted file mode 100644 index 601b1f1..0000000 --- a/assets/eip-4671/IERC4671Store.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IERC4671Store is IERC165 { - // Event emitted when a IERC4671Enumerable contract is added to the owner's records - event Added(address owner, address token); - - // Event emitted when a IERC4671Enumerable contract is removed from the owner's records - event Removed(address owner, address token); - - /// @notice Add a IERC4671Enumerable contract address to the caller's record - /// @param token Address of the IERC4671Enumerable contract to add - function add(address token) external; - - /// @notice Remove a IERC4671Enumerable contract from the caller's record - /// @param token Address of the IERC4671Enumerable contract to remove - function remove(address token) external; - - /// @notice Get all the IERC4671Enumerable contracts for a given owner - /// @param owner Address for which to retrieve the IERC4671Enumerable contracts - function get(address owner) external view returns (address[] memory); -} diff --git a/assets/eip-4675/.gitignore b/assets/eip-4675/.gitignore deleted file mode 100644 index 4015467..0000000 --- a/assets/eip-4675/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Dependency directory -node_modules - -# hardhat -cache -artifacts - -# macos -.DS_Store - -# IntelliJ IDE -.idea \ No newline at end of file diff --git a/assets/eip-4675/.solcover.js b/assets/eip-4675/.solcover.js deleted file mode 100644 index e532fc1..0000000 --- a/assets/eip-4675/.solcover.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - skipFiles: ['contracts/ERC20Token.sol'] -}; \ No newline at end of file diff --git a/assets/eip-4675/README.md b/assets/eip-4675/README.md deleted file mode 100644 index 5a14c32..0000000 --- a/assets/eip-4675/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Multi-Fractional Non-Fungible Token -Solidity Implementation of Multi-Fractional Non-Fungible Token. - -## Problem Trying to solve -Before, ERC20 Token contract should be deployed every time when fractionalizing a specific NFT. - -To solve this problem, this standard proposes a token standard to cover multiple fractionalized nft in a contract without having to deploy each time. - -Issue : https://github.com/ethereum/EIPs/issues/4674 - -PR : https://github.com/ethereum/EIPs/pull/4675 - -## How to use -``` -contracts/ - helper/ - interface/ - math/ - MFNFT.sol - NFT.sol - ERC20Token.sol -``` - -### Contracts -``MFNFT.sol`` : Multi-Fractional Non-Fungible Token Contract - -``NFT.sol`` : Non-Fungible Token Contract - -``ERC20Token.sol`` : Sample ERC-20 Token Contract - -``helper/Verifier.sol`` : Contract that verifies the ownership of NFT before fractionalization - -``math/SafeMath.sol`` : Openzeppelin SafeMath Library - -``interface/IERC20.sol`` : ERC-20 Token Interface - -``interface/IERC721.sol`` : ERC-721 Token Interface - -``interface/IMFNFT`` : MFNFT Token Interface - -### Install & Test - -Installation -``` -npm install -``` - -Test -``` -npx hardhat test -``` - -Coverage -``` -npx hardhat coverage -``` diff --git a/assets/eip-4675/contracts/ERC20Token.sol b/assets/eip-4675/contracts/ERC20Token.sol deleted file mode 100644 index 6b8f5fa..0000000 --- a/assets/eip-4675/contracts/ERC20Token.sol +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interface/IERC20.sol"; -import "./math/SafeMath.sol"; - -/** - * @title Standard RFT(ERC1633) token extending ERC20 - * - * @dev Implementation of the basic standard token. - * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md - * Originally based on code by FirstBlood: - * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol - * - * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for - * all accounts just by listening to said events. Note that this isn't required by the specification, and other - * compliant implementations may not do it. - */ -contract FT is IERC20 { - using SafeMath for uint256; - - mapping(address => uint256) private _balances; - - mapping(address => mapping(address => uint256)) private _allowed; - - uint256 private _totalSupply; - - // NFT Contract Address - address private _parentToken; - - // NFT ID of NFT(RFT) - TokenId - uint256 private _parentTokenId; - - // Admin Address to Set the Parent NFT - address private _admin; - - constructor(uint256 total_supply) { - _totalSupply = total_supply; - _balances[msg.sender] = total_supply; - _admin = msg.sender; - } - - /** - * @dev Total number of tokens in existence - */ - function totalSupply() public view override returns (uint256) { - return _totalSupply; - } - - /** - * @dev Gets the balance of the specified address. - * @param owner The address to query the balance of. - * @return An uint256 representing the amount owned by the passed address. - */ - function balanceOf(address owner) public view override returns (uint256) { - return _balances[owner]; - } - - /** - * @dev Function to check the amount of tokens that an owner allowed to a spender. - * @param owner address The address which owns the funds. - * @param spender address The address which will spend the funds. - * @return A uint256 specifying the amount of tokens still available for the spender. - */ - function allowance(address owner, address spender) - public - view - override - returns (uint256) - { - return _allowed[owner][spender]; - } - - /** - * @dev Transfer token for a specified address - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function transfer(address to, uint256 value) - public - override - returns (bool) - { - _transfer(msg.sender, to, value); - return true; - } - - /** - * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. - * Beware that changing an allowance with this method brings the risk that someone may use both the old - * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this - * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * @param spender The address which will spend the funds. - * @param value The amount of tokens to be spent. - */ - function approve(address spender, uint256 value) - public - override - returns (bool) - { - require(spender != address(0)); - - _allowed[msg.sender][spender] = value; - emit Approval(msg.sender, spender, value); - return true; - } - - /** - * @dev Transfer tokens from one address to another. - * Note that while this function emits an Approval event, this is not required as per the specification, - * and other compliant implementations may not emit the event. - * @param from address The address which you want to send tokens from - * @param to address The address which you want to transfer to - * @param value uint256 the amount of tokens to be transferred - */ - function transferFrom( - address from, - address to, - uint256 value - ) public override returns (bool) { - _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); - _transfer(from, to, value); - emit Approval(from, msg.sender, _allowed[from][msg.sender]); - return true; - } - - /** - * @dev Increase the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To increment - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param addedValue The amount of tokens to increase the allowance by. - */ - function increaseAllowance(address spender, uint256 addedValue) - public - returns (bool) - { - require(spender != address(0)); - - _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add( - addedValue - ); - emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); - return true; - } - - /** - * @dev Decrease the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To decrement - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param subtractedValue The amount of tokens to decrease the allowance by. - */ - function decreaseAllowance(address spender, uint256 subtractedValue) - public - returns (bool) - { - require(spender != address(0)); - - _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub( - subtractedValue - ); - emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); - return true; - } - - /** - * @dev Transfer token for a specified addresses - * @param from The address to transfer from. - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function _transfer( - address from, - address to, - uint256 value - ) internal { - require(to != address(0)); - - _balances[from] = _balances[from].sub(value); - _balances[to] = _balances[to].add(value); - emit Transfer(from, to, value); - } - - /** - * @dev Internal function that mints an amount of the token and assigns it to - * an account. This encapsulates the modification of balances such that the - * proper events are emitted. - * @param account The account that will receive the created tokens. - * @param value The amount that will be created. - */ - function _mint(address account, uint256 value) internal { - require(account != address(0)); - - _totalSupply = _totalSupply.add(value); - _balances[account] = _balances[account].add(value); - emit Transfer(address(0), account, value); - } - - /** - * @dev Internal function that burns an amount of the token of a given - * account. - * @param account The account whose tokens will be burnt. - * @param value The amount that will be burnt. - */ - function _burn(address account, uint256 value) internal { - require(account != address(0)); - - _totalSupply = _totalSupply.sub(value); - _balances[account] = _balances[account].sub(value); - emit Transfer(account, address(0), value); - } - - /** - * @dev Internal function that burns an amount of the token of a given - * account, deducting from the sender's allowance for said account. Uses the - * internal burn function. - * Emits an Approval event (reflecting the reduced allowance). - * @param account The account whose tokens will be burnt. - * @param value The amount that will be burnt. - */ - function _burnFrom(address account, uint256 value) internal { - _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( - value - ); - _burn(account, value); - emit Approval(account, msg.sender, _allowed[account][msg.sender]); - } -} diff --git a/assets/eip-4675/contracts/MFNFT.sol b/assets/eip-4675/contracts/MFNFT.sol deleted file mode 100644 index 098b4af..0000000 --- a/assets/eip-4675/contracts/MFNFT.sol +++ /dev/null @@ -1,346 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interface/IMFNFT.sol"; -import "./interface/IERC721.sol"; -import "./math/SafeMath.sol"; -import "./helper/Verifier.sol"; - -/** - * @title TWIG_FNFT Contract - */ -contract MFNFT is IMFNFT, Verifier { - using SafeMath for uint256; - - mapping(uint256 => mapping(address => uint256)) private _balances; - - mapping(uint256 => mapping(address => mapping(address => uint256))) - private _allowed; - - // uint256 private _totalSupply; - mapping(uint256 => uint256) _totalSupply; - - // NFT Contract Address - // address private _parentToken; - mapping(uint256 => address) _parentToken; - - // NFT ID of NFT(RFT) - TokenId - // uint256 private _parentTokenId; - mapping(uint256 => uint256) _parentTokenId; - - // - mapping(address => mapping(uint256 => uint256)) private _Ids; - - // Scalar value to distinguish fractionalized NFT - uint256 public _id; - - // Admin Address to Set the Parent NFT - address private _admin; - - // Event emitted when token is added - event TokenAddition( - address indexed token, - uint256 tokenId, - uint256 _id, - uint256 totalSupply - ); - - constructor() { - _admin = msg.sender; - } - - /** - * @dev onlyAdmin prohibits function calls arbitrary msg.sender - * except _admin - */ - modifier onlyAdmin() { - require(msg.sender == _admin); - _; - } - - /** - * @dev Mandatory function to receive NFT as a contract(CA) - * @return Bytes4 which is the selector of this function - */ - function onERC721Received( - address _operator, - address _from, - uint256 _tokenId, - bytes calldata _data - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } - - /** - * @dev (ERC165) Determines if this contract supports Re-FT(ERC1633). - * @param interfaceID The bytes4 to query if it matches with the contract interface id. - */ - function supportsInterface(bytes4 interfaceID) - external - pure - returns (bool) - { - return - interfaceID == this.supportsInterface.selector || // ERC165 - interfaceID == this.parentToken.selector || // parentToken() - interfaceID == this.parentTokenId.selector || // parentTokenId() - interfaceID == - this.parentToken.selector ^ this.parentTokenId.selector; // RFT - } - - /** - * @dev Sets the Address of NFT Contract Address & NFT Token ID - * @param parentNFTContractAddress The address NFT Contract address. - * @param parentNFTTokenId The token id of NFT. - */ - function setParentNFT( - address parentNFTContractAddress, - uint256 parentNFTTokenId, - uint256 totalSupply - ) public onlyAdmin { - require( - parentNFTContractAddress != address(0), - "MFNFT::setParentNFT: Parent NFT Contract should not be zero" - ); - require( - getTokenId(parentNFTContractAddress, parentNFTTokenId) == 0, - "MFNFT::setParentNFT: Already owned(fractionalized) by this contract" - ); - - verifyOwnership(parentNFTContractAddress, parentNFTTokenId); - - _id++; - - _Ids[parentNFTContractAddress][parentNFTTokenId] = _id; - - _parentToken[_id] = parentNFTContractAddress; - _parentTokenId[_id] = parentNFTTokenId; - - _totalSupply[_id] = totalSupply; - _balances[_id][msg.sender] = totalSupply; - - emit TokenAddition( - parentNFTContractAddress, - parentNFTTokenId, - _id, - totalSupply - ); - } - - /** - * @dev Returns the tokenId of with the given NFT information - * @return An uint256 value representing the tokenId of given NFT - */ - function getTokenId(address token, uint256 tokenId) - public - view - returns (uint256) - { - return _Ids[token][tokenId]; - } - - /** - * @dev Returns if the NFT is owned(fractionalized) by this contract. - * @return An bool representing whether the NFT is fractionalized by this contract - */ - function isRegistered(address token, uint256 tokenId) public view returns (bool) { - return (_Ids[token][tokenId] != 0); - } - - /** - * @dev Returns the Address of Parent Token Address - * @return An Address representing the address of NFT Contract this Re-FT is pointing to. - */ - function parentToken(uint256 tokenId) external view returns (address) { - return _parentToken[tokenId]; - } - - /** - * @dev Returns the Token ID of NFT - * @return An uint256 representing the token id of the NFT this Re-FT is pointing to. - */ - function parentTokenId(uint256 tokenId) external view returns (uint256) { - return _parentTokenId[tokenId]; - } - - /** - * @dev Total number of tokens in existence - */ - function totalSupply(uint256 tokenId) - public - view - override - returns (uint256) - { - return _totalSupply[tokenId]; - } - - /** - * @dev Gets the balance of the specified address. - * @param owner The address to query the balance of. - * @return An uint256 representing the amount owned by the passed address. - */ - function balanceOf(address owner, uint256 tokenId) - public - view - override - returns (uint256) - { - return _balances[tokenId][owner]; - } - - /** - * @dev Function to check the amount of tokens that an owner allowed to a spender. - * @param owner address The address which owns the funds. - * @param spender address The address which will spend the funds. - * @return A uint256 specifying the amount of tokens still available for the spender. - */ - function allowance( - address owner, - address spender, - uint256 tokenId - ) public view override returns (uint256) { - return _allowed[tokenId][owner][spender]; - } - - /** - * @dev Transfer token for a specified address - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function transfer( - address to, - uint256 tokenId, - uint256 value - ) public override returns (bool) { - _transfer(msg.sender, to, tokenId, value); - return true; - } - - /** - * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. - * Beware that changing an allowance with this method brings the risk that someone may use both the old - * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this - * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * @param spender The address which will spend the funds. - * @param value The amount of tokens to be spent. - */ - function approve( - address spender, - uint256 tokenId, - uint256 value - ) public override returns (bool) { - require(spender != address(0)); - - _allowed[tokenId][msg.sender][spender] = value; - emit Approval(msg.sender, spender, tokenId, value); - return true; - } - - /** - * @dev Transfer tokens from one address to another. - * Note that while this function emits an Approval event, this is not required as per the specification, - * and other compliant implementations may not emit the event. - * @param from address The address which you want to send tokens from - * @param to address The address which you want to transfer to - * @param value uint256 the amount of tokens to be transferred - */ - function transferFrom( - address from, - address to, - uint256 tokenId, - uint256 value - ) public override returns (bool) { - _allowed[tokenId][from][msg.sender] = _allowed[tokenId][from][ - msg.sender - ].sub(value); - _transfer(from, to, tokenId, value); - emit Approval( - from, - msg.sender, - tokenId, - _allowed[tokenId][from][msg.sender] - ); - return true; - } - - /** - * @dev Increase the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To increment - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param addedValue The amount of tokens to increase the allowance by. - */ - function increaseAllowance( - address spender, - uint256 tokenId, - uint256 addedValue - ) public returns (bool) { - require(spender != address(0)); - - _allowed[tokenId][msg.sender][spender] = _allowed[tokenId][msg.sender][ - spender - ].add(addedValue); - - emit Approval( - msg.sender, - spender, - tokenId, - _allowed[tokenId][msg.sender][spender] - ); - return true; - } - - /** - * @dev Decrease the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To decrement - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param subtractedValue The amount of tokens to decrease the allowance by. - */ - function decreaseAllowance( - address spender, - uint256 tokenId, - uint256 subtractedValue - ) public returns (bool) { - require(spender != address(0)); - - _allowed[tokenId][msg.sender][spender] = _allowed[tokenId][msg.sender][ - spender - ].sub(subtractedValue); - emit Approval( - msg.sender, - spender, - tokenId, - _allowed[tokenId][msg.sender][spender] - ); - return true; - } - - /** - * @dev Transfer token for a specified addresses - * @param from The address to transfer from. - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function _transfer( - address from, - address to, - uint256 tokenId, - uint256 value - ) internal { - require(to != address(0)); - - _balances[tokenId][from] = _balances[tokenId][from].sub(value); - _balances[tokenId][to] = _balances[tokenId][to].add(value); - - emit Transfer(from, to, tokenId, value); - } -} diff --git a/assets/eip-4675/contracts/NFT.sol b/assets/eip-4675/contracts/NFT.sol deleted file mode 100644 index 2dd6edb..0000000 --- a/assets/eip-4675/contracts/NFT.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract NFT is ERC721, ERC721Enumerable, ERC721Burnable, Ownable { - constructor() ERC721("MyToken", "MTK") {} - - function safeMint(address to, uint256 tokenId) public onlyOwner { - _safeMint(to, tokenId); - } - - function _baseURI() internal pure override returns (string memory) { - return "Test"; - } - - function _beforeTokenTransfer(address from, address to, uint256 tokenId) - internal - override(ERC721, ERC721Enumerable) - { - super._beforeTokenTransfer(from, to, tokenId); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(ERC721, ERC721Enumerable) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/helper/Verifier.sol b/assets/eip-4675/contracts/helper/Verifier.sol deleted file mode 100644 index de3af4c..0000000 --- a/assets/eip-4675/contracts/helper/Verifier.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -contract Verifier { - - function verifyOwnership( - address token, - uint256 tokenId - ) internal { - // bytes(keccak256(bytes('ownerOf(uint256)'))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x6352211e, tokenId)); - require( - success && (data.length == 32 && bytesToAddress(data) == address(this)), - "Verifier::verifyOwnership: NFT ownership verification failed" - ); - } - - function getOwner( - address token, - uint256 tokenId - ) internal returns(address) { - // bytes(keccak256(bytes('ownerOf(uint256)'))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x6352211e, tokenId)); - require( - success && (data.length == 32), - "Verifier::getOwner: NFT ownership verification failed" - ); - return bytesToAddress(data); - } - - function bytesToAddress(bytes memory bys) internal pure returns (address addr) { - assembly { - addr := mload(add(bys,32)) - } - } -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/interface/IERC20.sol b/assets/eip-4675/contracts/interface/IERC20.sol deleted file mode 100644 index 912040e..0000000 --- a/assets/eip-4675/contracts/interface/IERC20.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/** - * @title ERC20 interface - * @dev see https://github.com/ethereum/EIPs/issues/20 - */ -interface IERC20 { - function transfer(address to, uint256 value) external returns (bool); - - function approve(address spender, uint256 value) external returns (bool); - - function transferFrom(address from, address to, uint256 value) external returns (bool); - - function totalSupply() external view returns (uint256); - - function balanceOf(address who) external view returns (uint256); - - function allowance(address owner, address spender) external view returns (uint256); - - event Transfer(address indexed from, address indexed to, uint256 value); - - event Approval(address indexed owner, address indexed spender, uint256 value); -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/interface/IERC721.sol b/assets/eip-4675/contracts/interface/IERC721.sol deleted file mode 100644 index ecc6976..0000000 --- a/assets/eip-4675/contracts/interface/IERC721.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/** - * Contract that exposes the needed erc20 token functions - */ - -abstract contract IERC721 { - - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenaId - ); - - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - event ApprovalForAll( - address indexed owner, - address indexed operator, - bool approved - ); - - function balanceOf(address owner) public virtual view returns (uint256 balance); - function ownerOf(uint256 tokenId) public virtual view returns (address owner); - - function approve(address to, uint256 tokenId) public virtual; - function getApproved(uint256 tokenId) - public virtual view returns (address operator); - - function setApprovalForAll(address operator, bool _approved) public virtual; - function isApprovedForAll(address owner, address operator) - public virtual view returns (bool); - - function transferFrom(address from, address to, uint256 tokenId) public virtual; - function safeTransferFrom(address from, address to, uint256 tokenId) public virtual; - - -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/interface/IMFNFT.sol b/assets/eip-4675/contracts/interface/IMFNFT.sol deleted file mode 100644 index 1c36139..0000000 --- a/assets/eip-4675/contracts/interface/IMFNFT.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IMFNFT { - function transfer(address to, uint256 tokenId, uint256 value) external returns (bool); - - function approve(address spender, uint256 tokenId, uint256 value) external returns (bool); - - function transferFrom(address from, address to, uint256 tokenId, uint256 value) external returns (bool); - - function totalSupply(uint256 tokenId) external view returns (uint256); - - function balanceOf(address who, uint256 tokenId) external view returns (uint256); - - function allowance(address owner, address spender, uint256 tokenId) external view returns (uint256); - - event Transfer(address indexed from, address indexed to, uint256 tokenId, uint256 value); - - event Approval(address indexed owner, address indexed spender, uint256 tokenId, uint256 value); -} \ No newline at end of file diff --git a/assets/eip-4675/hardhat.config.ts b/assets/eip-4675/hardhat.config.ts deleted file mode 100644 index 044fd60..0000000 --- a/assets/eip-4675/hardhat.config.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { task } from "hardhat/config"; -import "@nomiclabs/hardhat-waffle"; -import "solidity-coverage"; -import "hardhat-deploy"; -import dotenv from "dotenv"; -import type { HardhatUserConfig, HttpNetworkUserConfig } from "hardhat/types"; - - - -// Load environment variables. -dotenv.config(); -const { NODE_URL, INFURA_KEY, MNEMONIC, ETHERSCAN_API_KEY, PK, SOLIDITY_VERSION, SOLIDITY_SETTINGS } = process.env; - -// Test mnemonic -const DEFAULT_MNEMONIC = - "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"; - -const sharedNetworkConfig: HttpNetworkUserConfig = {}; -if (PK) { - sharedNetworkConfig.accounts = [PK]; -} else { - sharedNetworkConfig.accounts = { - mnemonic: MNEMONIC || DEFAULT_MNEMONIC, - }; -} - -const primarySolidityVersion = SOLIDITY_VERSION || "0.8.0" - - -const userConfig: HardhatUserConfig = { - paths: { - artifacts: "artifacts", - cache: "cache", - deploy: "src/deploy", - sources: "contracts", - }, - solidity: { - compilers: [ - { version: primarySolidityVersion }, - { version: "0.6.12" }, - { version: "0.5.17" }, - { version: "0.7.5" }, - ] - }, - networks: { - hardhat: { - allowUnlimitedContractSize: true, - blockGasLimit: 100000000, - gas: 100000000 - }, - mainnet: { - ...sharedNetworkConfig, - url: `https://mainnet.infura.io/v3/${INFURA_KEY}`, - }, - xdai: { - ...sharedNetworkConfig, - url: "https://xdai.poanetwork.dev", - }, - ewc: { - ...sharedNetworkConfig, - url: `https://rpc.energyweb.org`, - }, - rinkeby: { - ...sharedNetworkConfig, - url: `https://rinkeby.infura.io/v3/${INFURA_KEY}`, - }, - goerli: { - ...sharedNetworkConfig, - url: `https://goerli.infura.io/v3/${INFURA_KEY}`, - }, - kovan: { - ...sharedNetworkConfig, - url: `https://kovan.infura.io/v3/${INFURA_KEY}`, - }, - volta: { - ...sharedNetworkConfig, - url: `https://volta-rpc.energyweb.org`, - }, - }, - namedAccounts: { - deployer: 0, - }, - mocha: { - timeout: 2000000, - }, - etherscan: { - apiKey: ETHERSCAN_API_KEY, - }, -} - -/** - * @type import('hardhat/config').HardhatUserConfig - */ -export default userConfig diff --git a/assets/eip-4675/package.json b/assets/eip-4675/package.json deleted file mode 100644 index e5728bf..0000000 --- a/assets/eip-4675/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "multi-fnft", - "version": "1.0.0", - "description": "Solidity Implementation of Multi-Fractional Non Fungible Token", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/PowerStream3604/multi-fnft.git" - }, - "keywords": [], - "author": "", - "license": "CC0-1.0", - "bugs": { - "url": "https://github.com/PowerStream3604/multi-fnft/issues" - }, - "homepage": "https://github.com/PowerStream3604/multi-fnft#readme", - "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.4", - "@nomiclabs/hardhat-waffle": "^2.0.1", - "@openzeppelin/contracts": "^4.4.1", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.0.0", - "@types/node": "^17.0.8", - "@types/yargs": "^17.0.8", - "chai": "^4.3.4", - "ethereum-waffle": "^3.4.0", - "ethers": "^5.5.3", - "hardhat": "^2.8.2", - "hardhat-deploy": "^0.9.24", - "solc": "^0.8.11", - "solidity-coverage": "^0.7.17", - "ts-node": "^10.4.0", - "typescript": "^4.5.4", - "yargs": "^17.3.1" - } -} diff --git a/assets/eip-4675/src/deploy/deploy_contracts.ts b/assets/eip-4675/src/deploy/deploy_contracts.ts deleted file mode 100644 index 297da1a..0000000 --- a/assets/eip-4675/src/deploy/deploy_contracts.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { DeployFunction } from "hardhat-deploy/types"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -const deploy: DeployFunction = async function ( - hre: HardhatRuntimeEnvironment, -) { - const { deployments, hardhatArguments, getNamedAccounts } = hre; - const { deployer } = await getNamedAccounts(); - const { deploy } = deployments; - - await deploy("MFNFT", { - from: deployer, - args: [], - log: true, - deterministicDeployment: true, - }); - - await deploy("NFT", { - from: deployer, - args: [], - log: true, - deterministicDeployment: true, - }); - -}; - -deploy.tags = ['contracts', 'MFNFT', 'NFT'] -export default deploy; diff --git a/assets/eip-4675/test/MFNFT.General.spec.ts b/assets/eip-4675/test/MFNFT.General.spec.ts deleted file mode 100644 index 25ab613..0000000 --- a/assets/eip-4675/test/MFNFT.General.spec.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { expect } from "chai"; -import hre, { deployments, ethers, waffle } from "hardhat"; -import { BigNumber } from "ethers"; -import { AddressZero } from "@ethersproject/constants"; -import { parseEther } from "@ethersproject/units"; -import { setMFNFTwithNFT, deployFTContract, getMFContract, getNFTContract, mintNFT } from "./utils/setup"; -import { transferFrom ,balanceOf, transfer, safeTransferFrom, addToken, approve, increaseAllowance, decreaseAllowance } from "./utils/execution"; - - -describe("Multi-Fractional Non-Fungible Token", async () => { - - const [admin, user1, user2, user3] = waffle.provider.getWallets(); - - // Scalar variable that gets incremented when token is added to MFNFT - const scalar_tokenId = 1; - - // token ID of the NFT - const tokenId = 1; - - // total supply of FT derived from NFT - const totalSupply = 1000; - - const setupTests = deployments.createFixture(async ({deployments}) => { - await deployments.fixture(); - return { - MFNFT: await getMFContract(), - NFT: await getNFTContract(), - } - }); - - describe("NFT Ownership", async () => { - it("should revert if NFT ownership is not given before token addition", async () => { - const { MFNFT, NFT } = await setupTests() - - await NFT.safeMint(user1.address, tokenId); - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply) - ).to.be.revertedWith("Verifier::verifyOwnership: NFT ownership verification failed") - }); - - it("should accept NFT after taking the ownership", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId); - - await addToken(MFNFT, NFT.address, tokenId, totalSupply) - }); - - it("should emit event for token addition", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId); - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply) - ).to.emit(MFNFT, "TokenAddition").withArgs(NFT.address, tokenId, 1, totalSupply) - }); - - it("should revert if given parentNFTContractAddress is zero", async () => { - const { MFNFT } = await setupTests() - - await expect( - addToken(MFNFT, AddressZero, tokenId, totalSupply) - ).to.be.revertedWith("MFNFT::setParentNFT: Parent NFT Contract should not be zero") - }); - - it("should revert if given parentNFTContractAddress doesn't support ERC-721", async() => { - const { MFNFT } = await setupTests(); - const FT = await deployFTContract(totalSupply); - - await expect ( - addToken(MFNFT, FT.address, tokenId, totalSupply) - ).to.be.reverted - }); - - it("should revert if setParentNFT() is called twice for the same NFT", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply); - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply) - ).to.be.revertedWith("MFNFT::setParentNFT: Already owned(fractionalized) by this contract") - }); - - it("should return true if NFT is owned & registered by MNFTContract -> isRegistered()", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply); - - expect(await MFNFT.isRegistered(NFT.address, tokenId)).to.be.eq(true) - }); - - it("should check if parentTokenContractAddress is set right", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - expect(await MFNFT.parentToken(scalar_tokenId)).to.be.eq(NFT.address) - }); - - it("should check if parentTokenId is set right", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - expect(await MFNFT.parentTokenId(scalar_tokenId)).to.be.eq(tokenId) - }); - - it("should check if totalSupply complys with designated value", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - expect(await MFNFT.totalSupply(scalar_tokenId)).to.be.equal(totalSupply) - }); - it("should check if _id is a scalar value that increases when token is added", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - const _id = await MFNFT.getTokenId(NFT.address, tokenId); - - await setMFNFTwithNFT(MFNFT, NFT, tokenId+1, totalSupply) - - expect(await MFNFT.getTokenId(NFT.address, tokenId+1)).to.be.equal(BigNumber.from(_id).add(1)); - }); - - }); - - describe("Admin", async () => { - - it("should check if admin can add new token", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId) - - await addToken(MFNFT, NFT.address, tokenId, totalSupply, {from: admin}); - }); - - it("should revert if non-admin tries to add token", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId) - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply, {from: user1}) - ).to.be.reverted - }); - - }); - - describe("onERC721Received", async () => { - - it("should be able to accept ERC-721 token with safeTransferFrom()", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, admin.address, tokenId) - - expect( - await safeTransferFrom(NFT, admin.address, MFNFT.address, tokenId) - ).to.emit(NFT, "Transfer").withArgs(admin.address, MFNFT.address, tokenId) - - expect(await NFT.ownerOf(tokenId)).to.be.equal(MFNFT.address) - }); - - it("should return expected value for onERC721Received()", async () => { - const { MFNFT } = await setupTests() - expect(await MFNFT.onERC721Received(admin.address, user1.address, tokenId, 0x0)).to.be.equal("0x150b7a02") - }); - - it("should return true if supportsInterface() receives supporting interface ID", async () => { - const { MFNFT } = await setupTests() - expect(await MFNFT.supportsInterface(0x01ffc9a7)).to.be.equal(true) - }) - - }); - - describe("Transfer & Allowance", async () => { - - const approvedValue = 100; - - it("should transfer exact amount of share to recipient", async () => { - const { MFNFT, NFT } = await setupTests() - - const transferAmount = 100; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - transfer(MFNFT, user1.address, scalar_tokenId, transferAmount) - ).to.emit(MFNFT, "Transfer").withArgs(admin.address, user1.address, scalar_tokenId, transferAmount) - - expect(await balanceOf(MFNFT, user1.address, scalar_tokenId)).to.be.equal(transferAmount) - }); - - it("should not be able to transfer more than balance", async () => { - const { MFNFT, NFT } = await setupTests() - - const transferAmount = 100; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - transfer(MFNFT, user1.address, scalar_tokenId, transferAmount + totalSupply) - ).to.be.reverted - }); - - it("should revert when trying to transfer to address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const transferAmount = 100; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - transfer(MFNFT, AddressZero, scalar_tokenId, transferAmount) - ).to.be.reverted - }); - - it("should check if approved user can spend on behalf", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - transferFrom(MFNFT, admin.address, user2.address, scalar_tokenId, approvedValue, {from: spender}) - ).to.emit(MFNFT, "Transfer").withArgs(admin.address, user2.address, scalar_tokenId, approvedValue) - }); - - it("should revert if user tries to approve address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = AddressZero; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender, scalar_tokenId, approvedValue) - ).to.be.reverted - }); - - it("should revert if user tries to use over approved amount", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - transferFrom(MFNFT, admin.address, user2.address, scalar_tokenId, approvedValue+100, {from: spender}) - ).to.be.reverted - }); - - it("should be able to increase allowance", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - increaseAllowance(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue * 2) - - expect(await MFNFT.allowance(admin.address, spender.address, tokenId)).to.be.equal(approvedValue * 2) - }); - - it("should revert if user tries to increase allowance for address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = AddressZero; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - increaseAllowance(MFNFT, AddressZero, scalar_tokenId, approvedValue * 2) - ).to.be.reverted - - expect(await MFNFT.allowance(admin.address, AddressZero, tokenId)).to.be.equal(0) - }) - - it("should be able to decrease allowance", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - decreaseAllowance(MFNFT, spender.address, scalar_tokenId, approvedValue / 2) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue / 2) - - expect(await MFNFT.allowance(admin.address, spender.address, tokenId)).to.be.equal(approvedValue / 2) - }); - - it("should revert if user tries to decrease allowance more than approved", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - decreaseAllowance(MFNFT, spender.address, scalar_tokenId, approvedValue * 2) - ).to.be.reverted - - expect(await MFNFT.allowance(admin.address, spender.address, tokenId)).to.be.equal(approvedValue) - }) - - it("should revert if user tries to decrease allowance for address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = AddressZero; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - decreaseAllowance(MFNFT, AddressZero, scalar_tokenId, approvedValue) - ).to.be.reverted - - expect(await MFNFT.allowance(admin.address, AddressZero, tokenId)).to.be.equal(0) - }) - }); - -}); \ No newline at end of file diff --git a/assets/eip-4675/test/utils/execution.ts b/assets/eip-4675/test/utils/execution.ts deleted file mode 100644 index 2d2a848..0000000 --- a/assets/eip-4675/test/utils/execution.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Contract, Wallet, utils, BigNumber, BigNumberish, Signer, PopulatedTransaction } from "ethers" -import { TypedDataSigner } from "@ethersproject/abstract-signer"; -import { AddressZero } from "@ethersproject/constants"; - -interface transOption { - from: string; - gas: BigInteger -} - -export const addToken = async (MFNFT: Contract, token: string, tokenId: BigNumberish, totalSupply: BigNumberish, options: any = {}): Promise => { - const tAddr = token || AddressZero; - const tId = tokenId || 1; - const tSupply = totalSupply || 1000; - - options.from == null ? options = null : (MFNFT = MFNFT.connect(options.from), options = null); - - return MFNFT.setParentNFT(tAddr, tId, tSupply, options); -} - - -export const safeTransferFrom = async (NFT: Contract, from: string, to: string, tokenId: BigNumberish) => { - return await NFT["safeTransferFrom(address,address,uint256)"](from, to, tokenId) -} - -export const transfer = async (MFNFT: Contract, to: string, _id: BigNumberish, value: BigNumberish, options: any = {}) => { - options.from == null ? options = null : (MFNFT = MFNFT.connect(options.from), options = null); - - return await MFNFT.transfer(to, _id, value) -} - -export const transferFrom = async (MFNFT: Contract, from: string, to: string, _id: BigNumberish, value: BigNumberish, options: any = {}) => { - options.from == null ? options = null : (MFNFT = MFNFT.connect(options.from), options = null); - - return await MFNFT.transferFrom(from, to, _id, value) -} - -export const balanceOf = async (MFNFT: Contract, owner: string, _id: BigNumberish) => { - return await MFNFT.balanceOf(owner, _id) -} - -export const approve = async (MFNFT: Contract, spender: string, tokenId: BigNumberish, value: BigNumberish) => { - return await MFNFT.approve(spender, tokenId, value) -} - -export const increaseAllowance = async (MFNFT: Contract, spender: string, tokenId: BigNumberish, value: BigNumberish) => { - return await MFNFT.increaseAllowance(spender, tokenId, value) -} - -export const decreaseAllowance = async (MFNFT: Contract, spender: string, tokenId: BigNumberish, value: BigNumberish) => { - return await MFNFT.decreaseAllowance(spender, tokenId, value) -} \ No newline at end of file diff --git a/assets/eip-4675/test/utils/setup.ts b/assets/eip-4675/test/utils/setup.ts deleted file mode 100644 index 957e517..0000000 --- a/assets/eip-4675/test/utils/setup.ts +++ /dev/null @@ -1,49 +0,0 @@ -import hre, { deployments } from "hardhat" -import { Contract, Wallet, utils, BigNumber, BigNumberish, Signer, PopulatedTransaction } from "ethers" -import { AddressZero } from "@ethersproject/constants"; -import { formatFixed, parseFixed } from "@ethersproject/bignumber"; -import { addToken } from "./execution"; -import { Address } from "cluster"; - -const MFContract = () => { - return "MFNFT"; -} - -export const getMFContract = async () => { - // const MFDeployment = await deployments.get(MFContract()); - const MF = await hre.ethers.getContractFactory(MFContract()); - // return MF.attach(MFDeployment.address); - return MF.deploy(); -} - -export const getNFTContract = async () => { - // const NFTDeployment = await deployments.get("NFT"); - const NFT = await hre.ethers.getContractFactory("NFT"); - // return NFT.attach(NFTDeployment.address); - return NFT.deploy(); -} - -export const deployFTContract = async (totalSupply: BigNumberish) => { - const tSupply = totalSupply || 1000; - const FT = await hre.ethers.getContractFactory("FT"); - return FT.deploy(tSupply); -} - -export const mintNFT = async (token: Contract, tokenOwner: string, tokenId:BigNumberish) => { - const NFT = token; - const tId = tokenId || 1; - const tOwner = tokenOwner || AddressZero; - return await NFT.safeMint(tOwner, tId); -} - -export const ownerOf = async (token: Contract, tokenId:BigNumberish) => { - const NFT = token; - const tId = tokenId || 1; - return await NFT.ownerOf(tId); -} - -export const setMFNFTwithNFT = async (MFNFT: Contract, NFT: Contract, tokenId: BigNumberish, totalSupply: BigNumberish) => { - await mintNFT(NFT, MFNFT.address, tokenId); - await addToken(MFNFT, NFT.address, tokenId, totalSupply); -} - diff --git a/assets/eip-4675/tsconfig.json b/assets/eip-4675/tsconfig.json deleted file mode 100644 index 8125850..0000000 --- a/assets/eip-4675/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "outDir": "dist" - }, - "include": ["./scripts", "./test"], - "files": ["./hardhat.config.ts"] -} \ No newline at end of file diff --git a/assets/eip-4824/daostar_context.jsonld b/assets/eip-4824/daostar_context.jsonld deleted file mode 100644 index d595fa6..0000000 --- a/assets/eip-4824/daostar_context.jsonld +++ /dev/null @@ -1,6 +0,0 @@ -{ - "@context": { - "@vocab": "http://daostar.org/schemas/", - "type": "@type" - } -} diff --git a/assets/eip-4824/daostar_ontology.ttl b/assets/eip-4824/daostar_ontology.ttl deleted file mode 100644 index 70b9116..0000000 --- a/assets/eip-4824/daostar_ontology.ttl +++ /dev/null @@ -1,262 +0,0 @@ -@prefix : . -@prefix owl: . -@prefix rdf: . -@prefix xml: . -@prefix xsd: . -@prefix rdfs: . -@base . - - rdf:type owl:Ontology . - -################################################################# -# Object Properties -################################################################# - -### http:/daostar.org/schemas/activities - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:label "activities"@en . - - -### http:/daostar.org/schemas/calls - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:comment "list of calls, which a proposal proposes"^^xsd:string ; - rdfs:label "calls"@en . - - -### http:/daostar.org/schemas/from - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:label "from"@en . - - -### http:/daostar.org/schemas/member - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range , - , - . - - -### http:/daostar.org/schemas/members - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range , - , - ; - rdfs:label "members"@en . - - -### http:/daostar.org/schemas/proposal - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:comment "proposal, which an activity relates to"^^xsd:string ; - rdfs:label "proposal"^^xsd:string . - - -### http:/daostar.org/schemas/proposals - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:comment "list of the DAO's proposals"^^xsd:string ; - rdfs:label "proposals"@en . - - -### http:/daostar.org/schemas/to - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:label "to"@en . - - -################################################################# -# Data properties -################################################################# - -### http:/daostar.org/schemas/activityLogURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "activityLogURI"@en . - - -### http:/daostar.org/schemas/address - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain , - ; - rdfs:range xsd:string ; - rdfs:label "address"@en . - - -### http:/daostar.org/schemas/callData - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:label "callData"@en . - - -### http:/daostar.org/schemas/contentURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:comment "URI pointing to the context of a proposal"^^xsd:string ; - rdfs:label "contentURI"@en . - - -### http:/daostar.org/schemas/data - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "call data"^^xsd:string ; - rdfs:label "data"@en . - - -### http:/daostar.org/schemas/description - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "a description of the DAO"^^xsd:string ; - rdfs:label "description"@en . - - -### http:/daostar.org/schemas/governanceURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "governanceURI"@en . - - -### http:/daostar.org/schemas/id - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain , - ; - rdfs:range xsd:string ; - rdfs:comment "proposal ID"^^xsd:string ; - rdfs:label "id"@en . - - -### http:/daostar.org/schemas/membersURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "membersURI"^^xsd:string . - - -### http:/daostar.org/schemas/name - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain , - ; - rdfs:range xsd:string ; - rdfs:comment "name of the DAO"^^xsd:string ; - rdfs:label "name"@en . - - -### http:/daostar.org/schemas/operation - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:label "call or delegate call"^^xsd:string , - "operation"@en . - - -### http:/daostar.org/schemas/proposalsURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "proposalsURI"@en . - - -### http:/daostar.org/schemas/status - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "status of a proposal"^^xsd:string ; - rdfs:label "status"@en . - - -### http:/daostar.org/schemas/value - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "call value"^^xsd:string ; - rdfs:label "value"^^xsd:string . - - -################################################################# -# Classes -################################################################# - -### http:/daostar.org/schemas/BlockchainAddress - rdf:type owl:Class ; - rdfs:comment "a blockchain address"^^xsd:string ; - rdfs:label "BlockchainAddress"@en . - - -### http:/daostar.org/schemas/CallDataEVM - rdf:type owl:Class ; - rdfs:comment "information regarding an EVM call"^^xsd:string ; - rdfs:label "CallDataEVM"@en . - - -### http:/daostar.org/schemas/DAO - rdf:type owl:Class ; - rdfs:label "DAO"@en . - - -### http:/daostar.org/schemas/EthereumAddress - rdf:type owl:Class ; - rdfs:subClassOf ; - rdfs:label "EthereumAddress"@en . - - -### http:/daostar.org/schemas/Proposal - rdf:type owl:Class ; - rdfs:label "Proposal"@en . - - -### http:/daostar.org/schemas/activity - rdf:type owl:Class ; - rdfs:comment "describes an interaction between a DAO member and a DAO proposal"^^xsd:string ; - rdfs:label "activity"@en . - - -### http:/daostar.org/schemas/member - rdf:type owl:Class . - - -################################################################# -# Annotations -################################################################# - - rdfs:comment "DAO member"^^xsd:string ; - rdfs:label "member"@en . - - -### Generated by the OWL API (version 4.5.13) https://github.com/owlcs/owlapi diff --git a/assets/eip-4881/deposit_snapshot.py b/assets/eip-4881/deposit_snapshot.py deleted file mode 100755 index 7c33275..0000000 --- a/assets/eip-4881/deposit_snapshot.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python3 -from __future__ import annotations -from typing import List, Optional, Tuple -from dataclasses import dataclass -from abc import ABC,abstractmethod -from eip_4881 import DEPOSIT_CONTRACT_DEPTH,Hash32,sha256,to_le_bytes,zerohashes - -@dataclass -class DepositTreeSnapshot: - finalized: List[Hash32, DEPOSIT_CONTRACT_DEPTH] - deposit_root: Hash32 - deposit_count: uint64 - execution_block_hash: Hash32 - execution_block_height: uint64 - - def calculate_root(self) -> Hash32: - size = self.deposit_count - index = len(self.finalized) - root = zerohashes[0] - for level in range(0, DEPOSIT_CONTRACT_DEPTH): - if (size & 1) == 1: - index -= 1 - root = sha256(self.finalized[index] + root) - else: - root = sha256(root + zerohashes[level]) - size >>= 1 - return sha256(root + to_le_bytes(self.deposit_count)) - def from_tree_parts(finalized: List[Hash32], - deposit_count: uint64, - execution_block: Tuple[Hash32, uint64]) -> DepositTreeSnapshot: - snapshot = DepositTreeSnapshot( - finalized, zerohashes[0], deposit_count, execution_block[0], execution_block[1]) - snapshot.deposit_root = snapshot.calculate_root() - return snapshot - -@dataclass -class DepositTree: - tree: MerkleTree - mix_in_length: uint - finalized_execution_block: Optional[Tuple[Hash32, uint64]] - def new() -> DepositTree: - merkle = MerkleTree.create([], DEPOSIT_CONTRACT_DEPTH) - return DepositTree(merkle, 0, None) - def get_snapshot(self) -> DepositTreeSnapshot: - assert(self.finalized_execution_block is not None) - finalized = [] - deposit_count = self.tree.get_finalized(finalized) - return DepositTreeSnapshot.from_tree_parts( - finalized, deposit_count, self.finalized_execution_block) - def from_snapshot(snapshot: DepositTreeSnapshot) -> DepositTree: - # decent validation check on the snapshot - assert(snapshot.deposit_root == snapshot.calculate_root()) - finalized_execution_block = (snapshot.execution_block_hash, snapshot.execution_block_height) - tree = MerkleTree.from_snapshot_parts( - snapshot.finalized, snapshot.deposit_count, DEPOSIT_CONTRACT_DEPTH) - return DepositTree(tree, snapshot.deposit_count, finalized_execution_block) - def finalize(self, eth1_data: Eth1Data, execution_block_height: uint64): - self.finalized_execution_block = (eth1_data.block_hash, execution_block_height) - self.tree.finalize(eth1_data.deposit_count, DEPOSIT_CONTRACT_DEPTH) - def get_proof(self, index: uint) -> Tuple[Hash32, List[Hash32]]: - assert(self.mix_in_length > 0) - # ensure index > finalized deposit index - assert(index > self.tree.get_finalized([]) - 1) - leaf, proof = self.tree.generate_proof(index, DEPOSIT_CONTRACT_DEPTH) - proof.append(to_le_bytes(self.mix_in_length)) - return leaf, proof - def get_root(self) -> Hash32: - return sha256(self.tree.get_root() + to_le_bytes(self.mix_in_length)) - def push_leaf(self, leaf: Hash32): - self.mix_in_length += 1 - self.tree = self.tree.push_leaf(leaf, DEPOSIT_CONTRACT_DEPTH) - -class MerkleTree(): - @abstractmethod - def get_root(self) -> Hash32: - pass - @abstractmethod - def is_full(self) -> bool: - pass - @abstractmethod - def push_leaf(self, leaf: Hash32, level: uint) -> MerkleTree: - pass - @abstractmethod - def finalize(self, deposits_to_finalize: uint, level: uint) -> MerkleTree: - pass - @abstractmethod - def get_finalized(self, result: List[Hash32]) -> uint: - # returns the number of finalized deposits in the tree - # while populating result with the finalized hashes - pass - def create(leaves: List[Hash32], depth: uint) -> MerkleTree: - if not(leaves): - return Zero(depth) - if not(depth): - return Leaf(leaves[0]) - split = min(2**(depth - 1), len(leaves)) - left = MerkleTree.create(leaves[0:split], depth - 1) - right = MerkleTree.create(leaves[split:], depth - 1) - return Node(left, right) - def from_snapshot_parts(finalized: List[Hash32], deposits: uint, level: uint) -> MerkleTree: - if not(finalized) or not(deposits): - # empty tree - return Zero(level) - if deposits == 2**level: - return Finalized(deposits, finalized[0]) - left_subtree = 2**(level - 1) - if deposits <= left_subtree: - left = MerkleTree.from_snapshot_parts(finalized, deposits, level - 1) - right = Zero(level - 1) - return Node(left, right) - else: - left = Finalized(left_subtree, finalized[0]) - right = MerkleTree.from_snapshot_parts(finalized[1:], deposits - left_subtree, level - 1) - return Node(left, right) - def generate_proof(self, index: uint, depth: uint) -> Tuple[Hash32, List[Hash32]]: - proof = [] - node = self - while depth > 0: - ith_bit = (index >> (depth - 1)) & 0x1 - if ith_bit == 1: - proof.append(node.left.get_root()) - node = node.right - else: - proof.append(node.right.get_root()) - node = node.left - depth -= 1 - proof.reverse() - return node.get_root(), proof - -@dataclass -class Finalized(MerkleTree): - deposit_count: uint - hash: Hash32 - def get_root(self) -> Hash32: - return self.hash - def is_full(self) -> bool: - return True - def finalize(self, deposits_to_finalize: uint, level: uint) -> MerkleTree: - return self - def get_finalized(self, result: List[Hash32]) -> uint: - result.append(self.hash) - return self.deposit_count - -@dataclass -class Leaf(MerkleTree): - hash: Hash32 - def get_root(self) -> Hash32: - return self.hash - def is_full(self) -> bool: - return True - def finalize(self, deposits_to_finalize: uint, level: uint) -> MerkleTree: - return Finalized(1, self.hash) - def get_finalized(self, result: List[Hash32]) -> uint: - return 0 - -@dataclass -class Node(MerkleTree): - left: MerkleTree - right: MerkleTree - def get_root(self) -> Hash32: - return sha256(self.left.get_root() + self.right.get_root()) - def is_full(self) -> bool: - return self.right.is_full() - def push_leaf(self, leaf: Hash32, level: uint) -> MerkleTree: - if not(self.left.is_full()): - self.left = self.left.push_leaf(leaf, level - 1) - else: - self.right = self.right.push_leaf(leaf, level - 1) - return self - def finalize(self, deposits_to_finalize: uint, level: uint) -> MerkleTree: - deposits = 2**level - if deposits <= deposits_to_finalize: - return Finalized(deposits, self.get_root()) - self.left = self.left.finalize(deposits_to_finalize, level - 1) - if deposits_to_finalize > deposits / 2: - remaining = deposits_to_finalize - deposits / 2 - self.right = self.right.finalize(remaining, level - 1) - return self - def get_finalized(self, result: List[Hash32]) -> uint: - return self.left.get_finalized(result) + self.right.get_finalized(result) - -@dataclass -class Zero(MerkleTree): - n: uint64 - def get_root(self) -> Hash32: - if self.n == DEPOSIT_CONTRACT_DEPTH: - # Handle the entirely empty tree case. This is included for - # consistency/clarity as the zerohashes array is typically - # only defined from 0 to DEPOSIT_CONTRACT_DEPTH - 1. - return sha256(zerohashes[self.n - 1] + zerohashes[self.n - 1]) - return zerohashes[self.n] - def is_full(self) -> bool: - return False - def push_leaf(self, leaf: Hash32, level: uint) -> MerkleTree: - return MerkleTree.create([leaf], level) - def get_finalized(self, result: List[Hash32]) -> uint: - return 0 - diff --git a/assets/eip-4881/deposit_tree_evolution.svg b/assets/eip-4881/deposit_tree_evolution.svg deleted file mode 100644 index 24e2598..0000000 --- a/assets/eip-4881/deposit_tree_evolution.svg +++ /dev/null @@ -1,5209 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/eip-4881/eip_4881.py b/assets/eip-4881/eip_4881.py deleted file mode 100755 index fbb74c5..0000000 --- a/assets/eip-4881/eip_4881.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -from dataclasses import dataclass -from hashlib import sha256 as SHA256 - -DEPOSIT_CONTRACT_DEPTH = 32 -Hash32 = bytes -Root = bytes -uint64 = int -BLSPubkey = bytes -Bytes32 = bytes -Gwei = uint64 -BLSSignature = bytes - -@dataclass -class DepositData: - pubkey: BLSPubkey - withdrawal_credentials: Bytes32 - amount: Gwei - signature: BLSSignature - -@dataclass -class Eth1Data: - deposit_root: Root - deposit_count: uint64 - block_hash: Hash32 - -def sha256(x) -> Hash32: - return SHA256(x).digest() -def to_le_bytes(i: int) -> bytes: - return i.to_bytes(32, byteorder='little') - -zerohashes = [b'\x00' * 32] -for i in range(1, DEPOSIT_CONTRACT_DEPTH): - zerohashes.append(sha256(zerohashes[i-1] + zerohashes[i-1])) - diff --git a/assets/eip-4881/test_cases.yaml b/assets/eip-4881/test_cases.yaml deleted file mode 100644 index ccb4569..0000000 --- a/assets/eip-4881/test_cases.yaml +++ /dev/null @@ -1,11011 +0,0 @@ ---- -- deposit_data: - pubkey: "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x97af5a5e14a033c35ec6489c585df8975026af431b2c5e9e3e6fd66089575bdffaf41c0a196d9b2ef822226c49d16214146f8a79b6ffa400109cd6c63c73afc7649d25533a3619547bc333b0cb3f303432fcd4951333b656d64d7b46a7ba764f" - deposit_data_root: "0x7af7da533b0dc64b690cb0604f5a81e40ed83796dd14037ea3a55383b8f0976a" - eth1_data: - deposit_root: "0x253f73460b66ba0b490a8f17029566b03c0690a584e262acc2be97c969bc65a6" - deposit_count: "1" - block_hash: "0xab6f0411b911f0d66539663dc6b41ed58bb4870cd3ae879e25c7bee8cd6d6f22" - block_height: 2 - snapshot: - finalized: - - "0x7af7da533b0dc64b690cb0604f5a81e40ed83796dd14037ea3a55383b8f0976a" - deposit_root: "0x253f73460b66ba0b490a8f17029566b03c0690a584e262acc2be97c969bc65a6" - deposit_count: 1 - execution_block_hash: "0xab6f0411b911f0d66539663dc6b41ed58bb4870cd3ae879e25c7bee8cd6d6f22" - execution_block_height: 2 -- deposit_data: - pubkey: "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb24d74bd23b52c41567305b6aecdc73dd53aea59fa997c0d6205531ce70cc32282dbf9963dde89297522fdc2c541eb0909472145805953a2298aa56160784c23b3905ed0ec17c4775b61cecb922a0d0e5241521387fc38184afe735c2ce399ad" - deposit_data_root: "0xb7dfec33825ba4dd8f6f8c2498c7202f07cbcf995a6de4b0abc71f1b1c46b2b2" - eth1_data: - deposit_root: "0x072080f22bf66504d6aa2b978c581e34637912ac191442af4f090dc5773d8936" - deposit_count: "2" - block_hash: "0x4e41a313cb3461e3154e76f87ec1bda35a48876529eaf3b99e335f43280c8d66" - block_height: 3 - snapshot: - finalized: - - "0xb6a04fb079b0153e6e555fd79bb89187c9386b2230f4020bd81558feca702982" - deposit_root: "0x072080f22bf66504d6aa2b978c581e34637912ac191442af4f090dc5773d8936" - deposit_count: 2 - execution_block_hash: "0x4e41a313cb3461e3154e76f87ec1bda35a48876529eaf3b99e335f43280c8d66" - execution_block_height: 3 -- deposit_data: - pubkey: "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xae64ef1d479919aa5cbd146559f366877298757843112239646d75658dc89d066e401be202a3198ffb7261b393b7f8c01901e36e3bfa17f7ae2620d5e44750fd983e3b97d9c44248689f81d6a0508501f2dadfbbc1c7ddcec307a4d6da42c59f" - deposit_data_root: "0x7b14766c0e7eddb146fc2a5bfd91e6d358d71995d2dc3c60d0c326607e39f7f2" - eth1_data: - deposit_root: "0x5e493befc0eb8dd0162eb48e5c3430882eefae51cb3fa757f11853b28387acdb" - deposit_count: "3" - block_hash: "0xb06534c2390ed7d7ec1d2630c6e8a340570a1a63df72c5cb81bb6ad65dce0692" - block_height: 4 - snapshot: - finalized: - - "0xb6a04fb079b0153e6e555fd79bb89187c9386b2230f4020bd81558feca702982" - - "0x7b14766c0e7eddb146fc2a5bfd91e6d358d71995d2dc3c60d0c326607e39f7f2" - deposit_root: "0x5e493befc0eb8dd0162eb48e5c3430882eefae51cb3fa757f11853b28387acdb" - deposit_count: 3 - execution_block_hash: "0xb06534c2390ed7d7ec1d2630c6e8a340570a1a63df72c5cb81bb6ad65dce0692" - execution_block_height: 4 -- deposit_data: - pubkey: "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa0b938a2e4cd395ca6f24b410ac60af8af115f93d869839d76d8ef5aaeee52350636d72ef264c25b20442a55b24cc82d09a21d416a3e957bb0d35baf53efb18ac274e2e744644d42fa8f17beb7b2b6baae5342fa06eb394da73dd45b1643f3e2" - deposit_data_root: "0x4760922c3a4ed6d321fc97355c4e8f7b7aa9e9771cb258095e52702c40170337" - eth1_data: - deposit_root: "0xe7d648159438c55bd12bbfea58ae16b9f9692d3fb05b0eac1729c67687dabe11" - deposit_count: "4" - block_hash: "0x63289eb1794f3ccd7536ce19023625e04777f84bd147c72b70eafcc6131b2f03" - block_height: 5 - snapshot: - finalized: - - "0x33ac367f73d5318defd4d79d1b16c69046401691739f1aac0753a84e0ea28d22" - deposit_root: "0xe7d648159438c55bd12bbfea58ae16b9f9692d3fb05b0eac1729c67687dabe11" - deposit_count: 4 - execution_block_hash: "0x63289eb1794f3ccd7536ce19023625e04777f84bd147c72b70eafcc6131b2f03" - execution_block_height: 5 -- deposit_data: - pubkey: "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaee3bec0f7786baae3a143131ea26d8ef62a8e17f7fade7355e42fb40a2717355d180c895f4de195523d3002d33ed60d10ba3b4285e8e712eb1612298051bdda70650966db509a104f374f45b16dfe06b21e6c385f0ff7d9c275b69dd193eef3" - deposit_data_root: "0xfba947b41bd711feba3294ddaf1a6a87b888472f08a6c06d48afa702881409cd" - eth1_data: - deposit_root: "0x1004652bf7077a95c5a7ad7f2abd71565b750dcfbb1748d9489f3cae54c961fb" - deposit_count: "5" - block_hash: "0xc6aab9995fee61fde7d100a4f3aea2e8c9091501179943781dd50049ba57723d" - block_height: 6 - snapshot: - finalized: - - "0x33ac367f73d5318defd4d79d1b16c69046401691739f1aac0753a84e0ea28d22" - - "0xfba947b41bd711feba3294ddaf1a6a87b888472f08a6c06d48afa702881409cd" - deposit_root: "0x1004652bf7077a95c5a7ad7f2abd71565b750dcfbb1748d9489f3cae54c961fb" - deposit_count: 5 - execution_block_hash: "0xc6aab9995fee61fde7d100a4f3aea2e8c9091501179943781dd50049ba57723d" - execution_block_height: 6 -- deposit_data: - pubkey: "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa28c8ad9d6cece980f888fe350f28ca3a4163f67f84c46053290e91134598fcba9fa7f4797c6cecdd435f44f65870e950a3b14c1d2ae162ee7958be3104e0489dd1a2f92482e30541abbd660e95d6476ad1373c19a64281b4057efdf36679758" - deposit_data_root: "0x277ce49d97fb149180999892fe334e8d4701430aa5e76b7539e9593edc74209d" - eth1_data: - deposit_root: "0x82219812154553443de49dc85542aad6702f3b7ddb12e12be143a83028a5254a" - deposit_count: "6" - block_hash: "0x9ea53b7ec3ca5c5668b6b100e4463b93bd76d888ca846fbbb08d8db9596a3bf3" - block_height: 7 - snapshot: - finalized: - - "0x33ac367f73d5318defd4d79d1b16c69046401691739f1aac0753a84e0ea28d22" - - "0x9c8cf1935559cf32ca28a3677d41dea8cbc511ba16d99d7b5ab5610ee22c9eb2" - deposit_root: "0x82219812154553443de49dc85542aad6702f3b7ddb12e12be143a83028a5254a" - deposit_count: 6 - execution_block_hash: "0x9ea53b7ec3ca5c5668b6b100e4463b93bd76d888ca846fbbb08d8db9596a3bf3" - execution_block_height: 7 -- deposit_data: - pubkey: "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x91d3bbe5ec01f47565f1b62e93b78fe084216a875154a6cb21cc521be15a548d3e28446c82fcf7214f484433abb2a0bb07c951cd3af2e2849e9e2456b4e53afbc122016e9f87a76269d307e02c495ef189a9bb48bf8a042a3a06676f82689000" - deposit_data_root: "0x7d552eeb22d6a4fa593784ebc22fbd2c400d922dc806def9d8899c06b301125d" - eth1_data: - deposit_root: "0x2e079bcdc6905fcdf19041d355d17dd5a74f0749ac2cb178e81b648aafef3908" - deposit_count: "7" - block_hash: "0x8b5a2c44e11f46011c61e43cd768efbfc5610381caea1968397dc3ef50e83d9f" - block_height: 8 - snapshot: - finalized: - - "0x33ac367f73d5318defd4d79d1b16c69046401691739f1aac0753a84e0ea28d22" - - "0x9c8cf1935559cf32ca28a3677d41dea8cbc511ba16d99d7b5ab5610ee22c9eb2" - - "0x7d552eeb22d6a4fa593784ebc22fbd2c400d922dc806def9d8899c06b301125d" - deposit_root: "0x2e079bcdc6905fcdf19041d355d17dd5a74f0749ac2cb178e81b648aafef3908" - deposit_count: 7 - execution_block_hash: "0x8b5a2c44e11f46011c61e43cd768efbfc5610381caea1968397dc3ef50e83d9f" - execution_block_height: 8 -- deposit_data: - pubkey: "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb3f76d7804668895192dd3fae3ccf101aa077da7c2678aefb8e92764e3bb0a32ef4592a62d8083aa0ca91f4cdefc745a0187fabd0f48fffc3213ccb9fca432a5c9c190d0d30f7c32f45e0ec4578eafad9a3f40e9a927e6cc1e0b5547cb54c0dc" - deposit_data_root: "0xad041a1626deaccb4f11900595eaaab0f5b35d3ad16c3f1e5155181421348c11" - eth1_data: - deposit_root: "0xdd303f6f720a21ca45aebdaaf06a051ef6ad0b86b786b8a671e67652f5b32846" - deposit_count: "8" - block_hash: "0xe736c69176be57cfecb91f6e5c56fd5ed0b1537a9cac8229e30eb1b86abac543" - block_height: 9 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - deposit_root: "0xdd303f6f720a21ca45aebdaaf06a051ef6ad0b86b786b8a671e67652f5b32846" - deposit_count: 8 - execution_block_hash: "0xe736c69176be57cfecb91f6e5c56fd5ed0b1537a9cac8229e30eb1b86abac543" - execution_block_height: 9 -- deposit_data: - pubkey: "0xa6d310dbbfab9a22450f59993f87a4ce5db6223f3b5f1f30d2c4ec718922d400e0b3c7741de8e59960f72411a0ee10a7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8be64cfe0334b009be7c2d7c601e561976c201551d27b79747fc11fcbee6b22b9bd87552f8d492ec383e8bec11045b8f0c91ac28995f5c4b1c56b4953bb7fa0144d32f2464e8990342c907a42221b2a5ff69b8301259fa87e0bcb5f4bdaa16de" - deposit_data_root: "0x9fb936887340fdde0663bc9811bc80c8939d4f1fda005a3d8d4d1e5803c65d43" - eth1_data: - deposit_root: "0x03d7883c2e63951bb4df714a7a0ceb389441999562dfa1fa98818b5791cf4c16" - deposit_count: "9" - block_hash: "0x597ffaa8cce4435d23a8f9dc28500422ce3d0e68fd5be5989eb032fb276b8293" - block_height: 10 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - - "0x9fb936887340fdde0663bc9811bc80c8939d4f1fda005a3d8d4d1e5803c65d43" - deposit_root: "0x03d7883c2e63951bb4df714a7a0ceb389441999562dfa1fa98818b5791cf4c16" - deposit_count: 9 - execution_block_hash: "0x597ffaa8cce4435d23a8f9dc28500422ce3d0e68fd5be5989eb032fb276b8293" - execution_block_height: 10 -- deposit_data: - pubkey: "0x9893413c00283a3f9ed9fd9845dda1cea38228d22567f9541dccc357e54a2d6a6e204103c92564cbc05f4905ac7c493a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x938c03a718bf6e456ec84d88a118939dec03dbdce3697b8ef0f52208835d3221b4a103997264e064b13ecb05bb4b8ef910f4f3446a3b1524549f53ed9e744c41c3bf0350c4bc9b29f2b223ab1a91abe1a153d80bda8cee1b4f2f8fe3e6409ff9" - deposit_data_root: "0xb6855e0bdbf1972d7a4571e6259cd589fe1baa3fa7ef569b9554c8e293b553dd" - eth1_data: - deposit_root: "0x2bba0e22d698d0e3cfa8374379f6c1a031a2aa0554cff8fa752936bc2ab8cdfd" - deposit_count: "10" - block_hash: "0x2e783a4e0b676caf552408ed8faf35f9e3e68dd2d82f71538ecf5c067dab6107" - block_height: 11 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - - "0x30a5058eb6db4b120d6c07b995376d1e1f013ccd24d52b1327d7528d2e50c970" - deposit_root: "0x2bba0e22d698d0e3cfa8374379f6c1a031a2aa0554cff8fa752936bc2ab8cdfd" - deposit_count: 10 - execution_block_hash: "0x2e783a4e0b676caf552408ed8faf35f9e3e68dd2d82f71538ecf5c067dab6107" - execution_block_height: 11 -- deposit_data: - pubkey: "0x876dd4705157eb66dc71bc2e07fb151ea53e1a62a0bb980a7ce72d15f58944a8a3752d754f52f4a60dbfc7b18169f268" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb7bffc1c83d9294af5df83f9889bd9c8d60f020b87d2a2a4235e8b0385b11b42a61432817210f7ca05cdfa074989fb57181a5a63b899402822c9f5dd33d1f199f441886cd6b753ff9fa87b72146f05c51b20d39c31a666dfdd00ea961accf1a3" - deposit_data_root: "0xc27c2db393fa0a74669bb5d2148ead286bec2fffc8c993e24a1c633221b0a91a" - eth1_data: - deposit_root: "0x697b75797e3f93acb334632bcd4af10979b12e10fa3f1b2dd7813bc31963f2aa" - deposit_count: "11" - block_hash: "0x3b9b8b3100433523f9b04ad853d9772795403b9610f58c71746f18280e7f1c97" - block_height: 12 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - - "0x30a5058eb6db4b120d6c07b995376d1e1f013ccd24d52b1327d7528d2e50c970" - - "0xc27c2db393fa0a74669bb5d2148ead286bec2fffc8c993e24a1c633221b0a91a" - deposit_root: "0x697b75797e3f93acb334632bcd4af10979b12e10fa3f1b2dd7813bc31963f2aa" - deposit_count: 11 - execution_block_hash: "0x3b9b8b3100433523f9b04ad853d9772795403b9610f58c71746f18280e7f1c97" - execution_block_height: 12 -- deposit_data: - pubkey: "0xaec922bd7a9b7b1dc21993133b586b0c3041c1e2e04b513e862227b9d7aecaf9444222f7e78282a449622ffc6278915d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8f2128ebe0cfc2ca5b6df5071b2960717a0b068dc4694d697af2589307ff0c4502e9faa5f7c5093ab8c214bce4f820db12336ea8853704fa050442ed6b513d68c6c18a33734d558658662c39174bb69be31b78d87eea4602292cda97fc2651ea" - deposit_data_root: "0x9ce8e449715c5c2b4f0d7da4de574f18ce429f44c4b1f648e23b8ece0c24bcc8" - eth1_data: - deposit_root: "0xa990ca5f9f41f0b305051de710d8b2fe78f63e369f408977a2cf19dcee718b60" - deposit_count: "12" - block_hash: "0xcec38f21aef8ce25842b3db7e9f5cb708a1aa9b3bad17c04f31ef963657f81b5" - block_height: 13 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - - "0x660335330368071f958f6fa763caa16650c7e3051733f84ce6bfa410f2052bf9" - deposit_root: "0xa990ca5f9f41f0b305051de710d8b2fe78f63e369f408977a2cf19dcee718b60" - deposit_count: 12 - execution_block_hash: "0xcec38f21aef8ce25842b3db7e9f5cb708a1aa9b3bad17c04f31ef963657f81b5" - execution_block_height: 13 -- deposit_data: - pubkey: "0x9314c6de0386635e2799af798884c2ea09c63b9f079e572acc00b06a7faccce501ea4dfc0b1a23b8603680a5e3481327" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xac36c7e15cd45f9f7c0e2672286513e03de301aff537460351bc1ccfa777139bfce29c6f441df8d7cbee4fdaea159cda1309a563ef28e9630ce1d022f8378046978fbb55e2f67dc708aa923e86bbfbd2487bf9f7b537a23ee1fd6c30043b6270" - deposit_data_root: "0x079946bfcb7488e966144acd500b247b685896812cdb13c138742dfaad33ce88" - eth1_data: - deposit_root: "0x96906b941533a885ac57be9f765f90b0c880174286913c515552167b0154a116" - deposit_count: "13" - block_hash: "0xe9ebca52836019409c578724469684df7ddbf2a3f1a106c66e3af1dae3b24ad8" - block_height: 14 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - - "0x660335330368071f958f6fa763caa16650c7e3051733f84ce6bfa410f2052bf9" - - "0x079946bfcb7488e966144acd500b247b685896812cdb13c138742dfaad33ce88" - deposit_root: "0x96906b941533a885ac57be9f765f90b0c880174286913c515552167b0154a116" - deposit_count: 13 - execution_block_hash: "0xe9ebca52836019409c578724469684df7ddbf2a3f1a106c66e3af1dae3b24ad8" - execution_block_height: 14 -- deposit_data: - pubkey: "0x903e2989e7442ee0a8958d020507a8bd985d3974f5e8273093be00db3935f0500e141b252bd09e3728892c7a8443863c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x86c8283b6c4c8c6f01ff005c62d0199583c99a81ca9207f3a539b63f1732a06eae9e16e601ebf7ac0ac6cb0c0d0f147d0e10c8a3f7877814ac6291ee8fe4c83591dc2a82680bcce53c09b0c41b71fa84be2f799257c020ae2c41c801953345db" - deposit_data_root: "0x8fe2405c8ed0d927f7c71d710879a4037b27409d0939074d040e88153245c1ad" - eth1_data: - deposit_root: "0x5d9c3452b70ec17728332003cf3d2e11d4f16151aef226ed799a837f4453e7a9" - deposit_count: "14" - block_hash: "0xa868c9bc5ac184f30ac39df5e5a5fdf0778575aad44b10e835968170c2183b7d" - block_height: 15 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - - "0x660335330368071f958f6fa763caa16650c7e3051733f84ce6bfa410f2052bf9" - - "0xb9d45d47c8373b0a9a2fe9c21bca36bf2a5fa9609c22e7699107a7d4fe8d39f1" - deposit_root: "0x5d9c3452b70ec17728332003cf3d2e11d4f16151aef226ed799a837f4453e7a9" - deposit_count: 14 - execution_block_hash: "0xa868c9bc5ac184f30ac39df5e5a5fdf0778575aad44b10e835968170c2183b7d" - execution_block_height: 15 -- deposit_data: - pubkey: "0x84398f539a64cbe01cfcd8c485ea51cd6657b94df93ee9b5dc61e1f18f69da6ca9d4dba63c956a81c68d5d4d4277a60f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8268676a3c7170a291d346859266a729b205077f7bd6c3b73d32ef63e420422aff7137abd008b22ae77db39803ff1c2a16990fd382d9ac7ea535daf73510fb9ad7b11c678bbec586a01ab0072ec47abc5adb498e1e8bf7bd957184b2c73d2a95" - deposit_data_root: "0x22d0b48ba191226ea3c0a8eb8f05dcc26a66678bd7b2e8f90e43b0a6afd57ab3" - eth1_data: - deposit_root: "0x17b149f19cec86287f4b6ff785698cc55366b29e5a3042df3548a6cc4b9256f8" - deposit_count: "15" - block_hash: "0xda2b6d573ca1a9f707971321b1a1c65ab93916f4b984d621f4368302e94ab102" - block_height: 16 - snapshot: - finalized: - - "0x24b5bc14c80886b849880b846493fef7c12b8eb723461442d52129ffdf7af6a1" - - "0x660335330368071f958f6fa763caa16650c7e3051733f84ce6bfa410f2052bf9" - - "0xb9d45d47c8373b0a9a2fe9c21bca36bf2a5fa9609c22e7699107a7d4fe8d39f1" - - "0x22d0b48ba191226ea3c0a8eb8f05dcc26a66678bd7b2e8f90e43b0a6afd57ab3" - deposit_root: "0x17b149f19cec86287f4b6ff785698cc55366b29e5a3042df3548a6cc4b9256f8" - deposit_count: 15 - execution_block_hash: "0xda2b6d573ca1a9f707971321b1a1c65ab93916f4b984d621f4368302e94ab102" - execution_block_height: 16 -- deposit_data: - pubkey: "0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9939cc7ccebfe4172d92680c74859e3f1d8de2e2a9bb494e9bec8306a5fb492bdab09f136f8f174d0bd5680991dc61970b5b7651ec3ffebe439b03df13e9259760bcfb1896795645796362856cd241b077d4d5e578bed13f7833cdee6135a7ce" - deposit_data_root: "0x72d791ecc2dc99d82f29f4cebc5df93a493091b4cd6d6b92b31fc92cac128bad" - eth1_data: - deposit_root: "0x1eceb67da690c4ae9c9664c51630c2094e3dc517c7473f2659babbe496366483" - deposit_count: "16" - block_hash: "0x0698420ee3e7ac4f8c314c740c2df8ddd50e3de679bd57b55e62c53b1d00bbe6" - block_height: 17 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - deposit_root: "0x1eceb67da690c4ae9c9664c51630c2094e3dc517c7473f2659babbe496366483" - deposit_count: 16 - execution_block_hash: "0x0698420ee3e7ac4f8c314c740c2df8ddd50e3de679bd57b55e62c53b1d00bbe6" - execution_block_height: 17 -- deposit_data: - pubkey: "0x8f467e5723deac7659e1ca273e28410cbaa6d495ab66ae77014f4cd21c64b6b5ab9987c9b5537fe0279bd063fe609be7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x95c61f4b48792bf54197d2f5db308ae3315901f4419b5a1823784fc85190adf17824aa80d884b1813a1ca3792d55612d04e28951014e7e573b0baf51fe7c956c8c6e37e25d353187a1c8633c37cb1d91bf0a90544c26fe5bf562dc99f1a52bb2" - deposit_data_root: "0x8df60288a3a0e60180b6026dcf71a62c5f88ae0714b433026155c49ab9f9a2b3" - eth1_data: - deposit_root: "0x6e612e99981156dbcf528144f5a661a3fec2092d32cf8276174a9f112d6f2694" - deposit_count: "17" - block_hash: "0x78f5fafef1f3dbc6ccde8362063b0ee74528976ef4f57407dbd3606fc87fa592" - block_height: 18 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0x8df60288a3a0e60180b6026dcf71a62c5f88ae0714b433026155c49ab9f9a2b3" - deposit_root: "0x6e612e99981156dbcf528144f5a661a3fec2092d32cf8276174a9f112d6f2694" - deposit_count: 17 - execution_block_hash: "0x78f5fafef1f3dbc6ccde8362063b0ee74528976ef4f57407dbd3606fc87fa592" - execution_block_height: 18 -- deposit_data: - pubkey: "0x8dde8306920812b32def3b663f7c540b49180345d3bcb8d3770790b7dc80030ebc06497feebd1bcf017d918f00bfa88f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa055036c86894b46ef0ef8e734b0a93cbeae393db9de264e57f03766ac1126ce21371a49dbb3d19c1da17e4f1c80eb1103e48c4ad44b778991ade181955b39928ca274a0625fe21bdb1ac79d8046a37a6066afc2e1fad405271efb80c4cec998" - deposit_data_root: "0x822c94e13fb37ecca034fabe44e24e06fd5e14685ffc98fc01dfb5d1b2ae8634" - eth1_data: - deposit_root: "0x4da8765913d4a494b45f34c4a4cc928e2be2e7e9c41dc13d69f5c0581f5bc628" - deposit_count: "18" - block_hash: "0xfdf087208f15f7cfbd0e777ee67d5a1224210c7095ec5a93201d1a042db10d27" - block_height: 19 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xd834f7120ed8a3f465b6484186992815aef250168d7c624b508c177ecbddb102" - deposit_root: "0x4da8765913d4a494b45f34c4a4cc928e2be2e7e9c41dc13d69f5c0581f5bc628" - deposit_count: 18 - execution_block_hash: "0xfdf087208f15f7cfbd0e777ee67d5a1224210c7095ec5a93201d1a042db10d27" - execution_block_height: 19 -- deposit_data: - pubkey: "0xab8d3a9bcc160e518fac0756d3e192c74789588ed4a2b1debf0c78f78479ca8edb05b12ce21103076df6af4eb8756ff9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x923fcfc77cf65ae29aa7771f9152140af8dfdf7301889aa52d5f1221b3ef8e7186b3dce969db0b6824308c657978952f01a4b2193d385fdd42ed107332b6714e4e10f70ad07919aadbf51d4f50ce0da40317bf8bc5d55b7d2e1bd8a77ff24a67" - deposit_data_root: "0x89be43c5944c2e25e15e930dd1c4572501359f2ae617eb829e96b83890517ae9" - eth1_data: - deposit_root: "0x2da633fb33c672f3348e994d49a8637935c8bf5db9c8e4524a19625f5c9bdb0f" - deposit_count: "19" - block_hash: "0x9b0e02e7dc57fb2c4178c55aa82cddd698a9eb91690a4ce1ce3508a3cb2589c0" - block_height: 20 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xd834f7120ed8a3f465b6484186992815aef250168d7c624b508c177ecbddb102" - - "0x89be43c5944c2e25e15e930dd1c4572501359f2ae617eb829e96b83890517ae9" - deposit_root: "0x2da633fb33c672f3348e994d49a8637935c8bf5db9c8e4524a19625f5c9bdb0f" - deposit_count: 19 - execution_block_hash: "0x9b0e02e7dc57fb2c4178c55aa82cddd698a9eb91690a4ce1ce3508a3cb2589c0" - execution_block_height: 20 -- deposit_data: - pubkey: "0x8d5d3672a233db513df7ad1e8beafeae99a9f0199ed4d949bbedbb6f394030c0416bd99b910e14f73c65b6a11fe6b62e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c0b2b26e5d9672cc95c0fa6d23ca6aa02a7163054a5492b95d5f4470083e4c2aca64e5d38972e13dceeea88996a1fca196e7e540fc05c96c195b3f27b421b38efa3c24f4fe6905c40eab900552531063f00b45718660179d36fa1daeb7e08e0" - deposit_data_root: "0x8c75a76c9c9892a587d7b0bc8c224d8a6e49d0513ee7f65f59b668d9108deca3" - eth1_data: - deposit_root: "0x6dee02182764197fb5be9df0535dbe15ca556b26e628d5aba8b080de4b92049b" - deposit_count: "20" - block_hash: "0xf32ee96bf62b97336de0e8846fc0d5ad58cf5b5cf899790859545d40644d00c4" - block_height: 21 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0x0e15a983c4aaa8cb3676b532c0dae58a86e1a329e2be55baa4e9bf8728bf7698" - deposit_root: "0x6dee02182764197fb5be9df0535dbe15ca556b26e628d5aba8b080de4b92049b" - deposit_count: 20 - execution_block_hash: "0xf32ee96bf62b97336de0e8846fc0d5ad58cf5b5cf899790859545d40644d00c4" - execution_block_height: 21 -- deposit_data: - pubkey: "0xa1c76af1545d7901214bb6be06be5d9e458f8e989c19373a920f0018327c83982f6a2ac138260b8def732cb366411ddc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9072a23d7159f70dcf6cda33337ded68b59d1caaa9b94b708d06736a8bdf9d414d126ccd9c53aa8baf5c3aa22163a1b013874c29db01d164c3a3632dc7ce73fdbd5d43ca64646418f4412f6d462122c115468faa4371836fcd43150daa696e1e" - deposit_data_root: "0x428fe5a8d16366f972a52daa8e2eba40bedf30360ecfa2d81efc2d68ee21c80d" - eth1_data: - deposit_root: "0x46ffc1bb9ee82fcf871ecdd525f8db26a0616c46644a150ef293d3a969af249f" - deposit_count: "21" - block_hash: "0x551285bb963663cc97445ab83591ca6e33d02ae26092c1897804438be2afa704" - block_height: 22 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0x0e15a983c4aaa8cb3676b532c0dae58a86e1a329e2be55baa4e9bf8728bf7698" - - "0x428fe5a8d16366f972a52daa8e2eba40bedf30360ecfa2d81efc2d68ee21c80d" - deposit_root: "0x46ffc1bb9ee82fcf871ecdd525f8db26a0616c46644a150ef293d3a969af249f" - deposit_count: 21 - execution_block_hash: "0x551285bb963663cc97445ab83591ca6e33d02ae26092c1897804438be2afa704" - execution_block_height: 22 -- deposit_data: - pubkey: "0x8dd74e1bb5228fc1fca274fda02b971c1003a4f409bbdfbcfec6426bf2f52addcbbebccdbf45eee6ae11eb5b5ee7244d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8bc79ba412ff7d26f58dcfa5b7f6c2b05570e1ac9c3671a9e2f22f11615b9fa5658a4532a35f5c42b5ffa136f796e04218d279e01990d6591daac0b7cb1481b5c7efe1264f39919dcf56e4b155ba162b7096ba9dd44805868793ab8440eac0a7" - deposit_data_root: "0x2862ae99f44e6fdb1d9ea652c10832e10334563e8873cc92af000703ee501849" - eth1_data: - deposit_root: "0xbb0311909b43eb5cc96107607d54315d84d24a552a27569a0992384417314e30" - deposit_count: "22" - block_hash: "0xd903cd669f26d87ed4d754ad13d3e8426505180dd67d9c3b44c3341fd2d33d8c" - block_height: 23 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0x0e15a983c4aaa8cb3676b532c0dae58a86e1a329e2be55baa4e9bf8728bf7698" - - "0xdf659f3a3a39eda83bcbebb3a7ce225bda424b28d47ad4fd78a187e94150474e" - deposit_root: "0xbb0311909b43eb5cc96107607d54315d84d24a552a27569a0992384417314e30" - deposit_count: 22 - execution_block_hash: "0xd903cd669f26d87ed4d754ad13d3e8426505180dd67d9c3b44c3341fd2d33d8c" - execution_block_height: 23 -- deposit_data: - pubkey: "0x954eb88ed1207f891dc3c28fa6cfdf8f53bf0ed3d838f3476c0900a61314d22d4f0a300da3cd010444dd5183e35a593c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x942f0eacead041b7fb92f1917f46e3a884beded26a90aed33b571ea20a88a2b6c2b3bfb39bb961e8d5a9003ae9f95d0e00f5c405ede3135de09749dae540fbd5bc3c89ff1c3494676298caca4a29277aa3f3633fa827fa3b32f3ce9fb71b2486" - deposit_data_root: "0x171d4e93ba95da64b2a59446bcba72a5af8cb33cd002d8db1774111b851b4c2f" - eth1_data: - deposit_root: "0xe1367c132b2c346fb43358ed1d2c392c535070c065450c8cc45c387ba99eeb6a" - deposit_count: "23" - block_hash: "0x457a64cc460ff02516925473033c4f5a16a0be8c98f5db1c6cfd4aa9001ebaa9" - block_height: 24 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0x0e15a983c4aaa8cb3676b532c0dae58a86e1a329e2be55baa4e9bf8728bf7698" - - "0xdf659f3a3a39eda83bcbebb3a7ce225bda424b28d47ad4fd78a187e94150474e" - - "0x171d4e93ba95da64b2a59446bcba72a5af8cb33cd002d8db1774111b851b4c2f" - deposit_root: "0xe1367c132b2c346fb43358ed1d2c392c535070c065450c8cc45c387ba99eeb6a" - deposit_count: 23 - execution_block_hash: "0x457a64cc460ff02516925473033c4f5a16a0be8c98f5db1c6cfd4aa9001ebaa9" - execution_block_height: 24 -- deposit_data: - pubkey: "0xaf344fce60dbd5fb850070e6e76a065e1a32485245ef4f413135a86ae703da88407c5d01c71f6bb06a151ff96cca7191" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x968b1f4d64fbec9c8b5599eb9f3ebf64ea9f5cda3cde3c463cd714022efad0653563a2f24581f2f6c019567abd4801400b79d097b925d363aed6e995f9d1869dbd4ae52ea3542a6a2258094867c767c7e9951e80df872dce35904b6d8eb13c49" - deposit_data_root: "0x4f24e0bcfcbf2a0e0828b5b63fed6f175c939c6332d622d5959b683ff5a986ba" - eth1_data: - deposit_root: "0x75580add56b9161b8747e19faba42030d70994396de5ab95b6b86f61262d86ae" - deposit_count: "24" - block_hash: "0x8d44620a0a0ff3d00477c7735e28cfb742a10619664a017f8800ea13dcdcef9a" - block_height: 25 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - deposit_root: "0x75580add56b9161b8747e19faba42030d70994396de5ab95b6b86f61262d86ae" - deposit_count: 24 - execution_block_hash: "0x8d44620a0a0ff3d00477c7735e28cfb742a10619664a017f8800ea13dcdcef9a" - execution_block_height: 25 -- deposit_data: - pubkey: "0xae241af60691fda1cf8ca44d49573c55818c53b6141800cca2d488b9a3fba71c0f869179fff50c084657831fbeb42bf4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa439766948de7ba95f03ae54671d0c71bf34538405554f9a6b52e4b3672ab8f8b484841844652fa2573a3473fbd6d5d2046f6776ae161384da704df0b0dd8d800b4b0179e88ce193f84bf022fb81490bb4697f331eebdbfcab1c09b76c5f798a" - deposit_data_root: "0x98c8fddae908c9740b1e57526c08791223bf0fa2d03cee0855a62ba3af995483" - eth1_data: - deposit_root: "0x17ab1dfcb9c9438066726df5d532f91d74290292887618fdb5392f7a4da88d3f" - deposit_count: "25" - block_hash: "0x0d92259ff68bc51a4dad01a711927f941fcde206c05ef405d37f44e602ca0513" - block_height: 26 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - - "0x98c8fddae908c9740b1e57526c08791223bf0fa2d03cee0855a62ba3af995483" - deposit_root: "0x17ab1dfcb9c9438066726df5d532f91d74290292887618fdb5392f7a4da88d3f" - deposit_count: 25 - execution_block_hash: "0x0d92259ff68bc51a4dad01a711927f941fcde206c05ef405d37f44e602ca0513" - execution_block_height: 26 -- deposit_data: - pubkey: "0x96746aaba64dc87835ba709332f4d5d7837ada092b439c49d251aecf92aab5dc132e917bf6f59799bc093f976a7bc021" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa147ee89efcadbe1c8ae5dd2309b9153966d57d0c69b47f33f1a2a0bfa47821956ea5b20ffdfd77602509e7c92b4cf70ac7e16593c9325e41869af6e6844a09f20273001cbd00f413690c913b8c61390e40181c3c78d0605db457b17165535b" - deposit_data_root: "0xc220f1e62f775aa20f6783a08ede02364bb656aa3aea8a53b566cc3f5780ce20" - eth1_data: - deposit_root: "0x28533734876a9a4da9bc4518a561f124e9c09a3c52e727707cbbb2bae1a8afd4" - deposit_count: "26" - block_hash: "0xc11cc9313a9f6f28d1f4b6f06a0f4871efcc548df79c90c77b10b6bd6af073b7" - block_height: 27 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - - "0x16bdf648ff50bb5f33cab70a7cff595ba63caaff4fcb670610e802cc344dffc2" - deposit_root: "0x28533734876a9a4da9bc4518a561f124e9c09a3c52e727707cbbb2bae1a8afd4" - deposit_count: 26 - execution_block_hash: "0xc11cc9313a9f6f28d1f4b6f06a0f4871efcc548df79c90c77b10b6bd6af073b7" - execution_block_height: 27 -- deposit_data: - pubkey: "0xb9d1d914df3d4565465c3fd52b5b96e637f9980570cabf5b5d4aadf5a329ac36ad672819d997e735f5052e28b1f0c104" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2a21801a7ddd00d4b1df1c571b74b95b7cb92d484712349c612438183e4d486113f51e356343be7835a0f6c88e7de5a0978cb3815b3ca044fe741c25b02eade6fb36500b2264305b5954cc4f5ad451c6f43d552405def57a07411a32016097b" - deposit_data_root: "0x6db78e60584351fc046ce86f6f56c1ec42ecc5526677d4a1b3bb720280277f38" - eth1_data: - deposit_root: "0xf18ab78c3cfee43fc0c09c7c1101d655b86b30809149134e0fd6603247b48650" - deposit_count: "27" - block_hash: "0x6381e5f3e249642bb56562bb2cd645d77578491d8b22b4de49976c606f10b275" - block_height: 28 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - - "0x16bdf648ff50bb5f33cab70a7cff595ba63caaff4fcb670610e802cc344dffc2" - - "0x6db78e60584351fc046ce86f6f56c1ec42ecc5526677d4a1b3bb720280277f38" - deposit_root: "0xf18ab78c3cfee43fc0c09c7c1101d655b86b30809149134e0fd6603247b48650" - deposit_count: 27 - execution_block_hash: "0x6381e5f3e249642bb56562bb2cd645d77578491d8b22b4de49976c606f10b275" - execution_block_height: 28 -- deposit_data: - pubkey: "0x963528adb5322c2e2c54dc296ffddd2861bb103cbf64646781dfa8a3c2d8a8eda7079d2b3e95600028c44365afbf8879" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa52e6582b3f70257211e96a5023f5f860d6c9a236ddd1362f8bf354a3621cee74500682ef4e10f725f19a15527c086320fe2714745b54b4be4cbf1d6ff3f84821bd9e789717ad9fcfc36f9a96509fe4889c0c7824f4e0ce21108744f369d1c07" - deposit_data_root: "0x7e238e9a9674ddc5e0913d2f698cb776a64392c390353e42287f7c5f4098168a" - eth1_data: - deposit_root: "0x4fa046ca0d14d29f272ee56ef063b382a46a04b30cf2826dd0c832152a48d476" - deposit_count: "28" - block_hash: "0xf6c4330660f686d3c0549237f8331f78fdb99653b72a25d5b88b7a26be512ae3" - block_height: 29 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - - "0xf7068f7d6c4ca26e61c3ea98cde4d5de184da7fba2f5a99ea5de37c719e27a07" - deposit_root: "0x4fa046ca0d14d29f272ee56ef063b382a46a04b30cf2826dd0c832152a48d476" - deposit_count: 28 - execution_block_hash: "0xf6c4330660f686d3c0549237f8331f78fdb99653b72a25d5b88b7a26be512ae3" - execution_block_height: 29 -- deposit_data: - pubkey: "0xb245d63d3f9d8ea1807a629fcb1b328cb4d542f35a3d5bc478be0df389dddd712fc4c816ba3fede9a96320ae6b24a7d8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8a177ab3d2b199d3f8bd8dea6b896e1456559e2205064eca589ff1150ad4a4cefb3307fbe1eab515b1caae1d9bd4181f106e1371e3f11d22dd76d90589dcad6b9d9317c7a403dcee09b5074bed807fe6e57c85d688956ad439232d20bdcfc069" - deposit_data_root: "0xe71a456a9faa68cca752c81c90d954138a586b0f8166c8870ea89b54b01313b3" - eth1_data: - deposit_root: "0xc71512461c08e8fab3ad7d23413a46e9c47f3d133ce181800484b6f4603bcd55" - deposit_count: "29" - block_hash: "0xd11a018e251c410ef7672f160087e677d29da1b591e14ac632fa2a1c9e00b62f" - block_height: 30 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - - "0xf7068f7d6c4ca26e61c3ea98cde4d5de184da7fba2f5a99ea5de37c719e27a07" - - "0xe71a456a9faa68cca752c81c90d954138a586b0f8166c8870ea89b54b01313b3" - deposit_root: "0xc71512461c08e8fab3ad7d23413a46e9c47f3d133ce181800484b6f4603bcd55" - deposit_count: 29 - execution_block_hash: "0xd11a018e251c410ef7672f160087e677d29da1b591e14ac632fa2a1c9e00b62f" - execution_block_height: 30 -- deposit_data: - pubkey: "0xa98ed496c2f464226500a6ce04602ff9ef133ed6316f372f6c744aee165149f7e578b12780e0eacec307ae6907351d99" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa12d7c9738f2e443bb7226e03631847b82b84fa2a77426b101dd4c0574da816abfbd0905df5e00d4217ac9ea10b0213c09f6ed2fe8628f8e15c8e07d7d7809cefebb0d9dc515b2f2603e6fba7854c8c842b70137e0f5dea4e2f37ca6b116d8dc" - deposit_data_root: "0x9e5bfab0b9bd4cbf7232f3a004074c87974e1e1c98838a99775355443426bb6c" - eth1_data: - deposit_root: "0xc0108c1784340504324dc235e2214f51603386e6d7d1a36a36adf67148a19a66" - deposit_count: "30" - block_hash: "0xf50b59b6a0f164a687d5388bc590467f68a874769b2e320a0c83f4a01dcd663c" - block_height: 31 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - - "0xf7068f7d6c4ca26e61c3ea98cde4d5de184da7fba2f5a99ea5de37c719e27a07" - - "0x011f1eb3813fb8ee88932f9e3ab2781c9c085105658ba15559994d2e26a1d8dd" - deposit_root: "0xc0108c1784340504324dc235e2214f51603386e6d7d1a36a36adf67148a19a66" - deposit_count: 30 - execution_block_hash: "0xf50b59b6a0f164a687d5388bc590467f68a874769b2e320a0c83f4a01dcd663c" - execution_block_height: 31 -- deposit_data: - pubkey: "0xae00fc3de831b09661a0ac02873c45c84cb2b58cffb6430a3f607e4c3fa1e0932397f11307cd169cdc6f79c463527260" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x84ea7a055239b25ec73f3ac4f12e92941c3cd05a35a72f9a1ee8267cdbc8b97e9ee32e52262b87563186d716ead86d2f01d7ae34575579a6348d9085154b0ea1f186374f169c251e621e1fd177a7fffcb1af3feac4555092ef628a090de37a85" - deposit_data_root: "0x661bd0e0ddbd2ba4acb89aa575180637c85f5413ed1bcdc028ab1abd0cfc2b47" - eth1_data: - deposit_root: "0x832e7610fb4663859d3d72f21510c20aede5e3a5dfc5ac4434c5800ebfd78444" - deposit_count: "31" - block_hash: "0x5eceb3df2ef05c68164fa475e0e0e0e2ec5f5613dd8bfe1c486a172dbf0ae594" - block_height: 32 - snapshot: - finalized: - - "0x5f652df37d6370cd7a4267959f0b885e201b293a69f57a8769c92d797050967e" - - "0xf411283523e35f406ed67667dab5e352fc257052ddac82c3bb78c1cf157884aa" - - "0xf7068f7d6c4ca26e61c3ea98cde4d5de184da7fba2f5a99ea5de37c719e27a07" - - "0x011f1eb3813fb8ee88932f9e3ab2781c9c085105658ba15559994d2e26a1d8dd" - - "0x661bd0e0ddbd2ba4acb89aa575180637c85f5413ed1bcdc028ab1abd0cfc2b47" - deposit_root: "0x832e7610fb4663859d3d72f21510c20aede5e3a5dfc5ac4434c5800ebfd78444" - deposit_count: 31 - execution_block_hash: "0x5eceb3df2ef05c68164fa475e0e0e0e2ec5f5613dd8bfe1c486a172dbf0ae594" - execution_block_height: 32 -- deposit_data: - pubkey: "0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x95e0bd85c20f14f8eb05bb4f2d6a5a0297c78c6598fcc886fa93d2bee73b52c8913a848f690be3094883fbe31200bae90a1401d4c8b11dc82c635f63a3f42cef8fb19f04b27e7ec67c03811327614242d407b157b852e008df78fe482c3496ac" - deposit_data_root: "0xc8d8e9014b8fd00b885838bd0e71fcec16dbdef5b5b5cae3b09176d8e6bf7883" - eth1_data: - deposit_root: "0x2def103e29ea70f435350e0a77e1f8cafe671db9d9f43a1d20621abc7f85c73a" - deposit_count: "32" - block_hash: "0x6476bebf6ce3c9e8cbc630a58962e9891b99320d548ea03e527ebd3d23f3c35c" - block_height: 33 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - deposit_root: "0x2def103e29ea70f435350e0a77e1f8cafe671db9d9f43a1d20621abc7f85c73a" - deposit_count: 32 - execution_block_hash: "0x6476bebf6ce3c9e8cbc630a58962e9891b99320d548ea03e527ebd3d23f3c35c" - execution_block_height: 33 -- deposit_data: - pubkey: "0xa9cf360aa15fb1d1d30ee2b578dc5884823c19661886ae8b892775ccb3bd96b7d7345569a2aa0b14e4d015c54a6a0c54" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x87e60e3980d5d71bcc9983c3317ea8a1a60862ea9f4a4d3c3c710ddbd90db3dac49d6ff7c9d2f8c447a02f9310d7e2eb119e06b83543bc0918c87199ba2724624ece384c336e69743850dd4b3b92f560423279bf50252546d612107a03bab3d8" - deposit_data_root: "0x1051a6ad73280cee0f10ba9c5762e001a23aef44269a7eae9b2050d17ac1b2cd" - eth1_data: - deposit_root: "0x2260d0f4b57dd958fdc8badd66391e11e13bfdb64563ce7904bec1ddc4f8ece1" - deposit_count: "33" - block_hash: "0x0268dec10d6ad09835aa9a0a2fe971f4b4ef81f59aaf8a9d43d99f17c186964a" - block_height: 34 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0x1051a6ad73280cee0f10ba9c5762e001a23aef44269a7eae9b2050d17ac1b2cd" - deposit_root: "0x2260d0f4b57dd958fdc8badd66391e11e13bfdb64563ce7904bec1ddc4f8ece1" - deposit_count: 33 - execution_block_hash: "0x0268dec10d6ad09835aa9a0a2fe971f4b4ef81f59aaf8a9d43d99f17c186964a" - execution_block_height: 34 -- deposit_data: - pubkey: "0xaef9162ee6f29ee82fbfe387756d84f9ac472eb8709217aaf28f5ef0ea273f6210e531496470b30d2b7747216e3672d5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5dc53467b3e5826af953385e83f5a21858c7019c6c87aad005509038379db54e0d7d48453bb00394f78e1895a3af296147c45f766f4eafad06e60e7d2b206d628ba2cc61db59a472c68cf48b28979bfccf28a4cafac63271bfcce7461450b5e" - deposit_data_root: "0x6c24e556eec47c66d6b2a3ec29e013ba206ea34e8d3685b90ff823b06aa80b4b" - eth1_data: - deposit_root: "0x9268f23209182ea5393b84731c67d205b50dd541b590548479d816be20fdee70" - deposit_count: "34" - block_hash: "0x69ef65f0294564315ef609e7b5bf76420ced3fe110c763dd8bcd348760b1a267" - block_height: 35 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0x70918d35011dbe3749fad7b8ca02046cb69b4893834ff0f71cfe11a17f2fde0c" - deposit_root: "0x9268f23209182ea5393b84731c67d205b50dd541b590548479d816be20fdee70" - deposit_count: 34 - execution_block_hash: "0x69ef65f0294564315ef609e7b5bf76420ced3fe110c763dd8bcd348760b1a267" - execution_block_height: 35 -- deposit_data: - pubkey: "0xb7e6e187ed813d950a9a17d1e70c03e4de2903596c4c5ff326848515c985deee38198efebc265300cd4f1d6bd7b5d264" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x886d08aca274c127f741d249a865851818a8bd387d1eec83f2d702c4ce78e6ba93d3ddc629d0269f43a91b2a56074797044f37c528b10344cd08808f829867577aa8aed839f6066922ed4953376b65ea14ba725478674bf32a37e337563774cc" - deposit_data_root: "0x72c9a1bdb8a1deaae1c4007567257ebad96b664c27c9accfab8ad9f9d10b8ca1" - eth1_data: - deposit_root: "0x5261a6f04d75e281c1c83bd3a443746b643d0956a23cb2ce69d39c0504072038" - deposit_count: "35" - block_hash: "0xd54072d4f8010162a877d38f5d814d9a05ec3ffacdf04128c86205cbc9de92aa" - block_height: 36 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0x70918d35011dbe3749fad7b8ca02046cb69b4893834ff0f71cfe11a17f2fde0c" - - "0x72c9a1bdb8a1deaae1c4007567257ebad96b664c27c9accfab8ad9f9d10b8ca1" - deposit_root: "0x5261a6f04d75e281c1c83bd3a443746b643d0956a23cb2ce69d39c0504072038" - deposit_count: 35 - execution_block_hash: "0xd54072d4f8010162a877d38f5d814d9a05ec3ffacdf04128c86205cbc9de92aa" - execution_block_height: 36 -- deposit_data: - pubkey: "0x81054bd51ce57a8415f0c8e0f2fbf94f5a8464552baa33263c20a4da062e5ed994a4d32c171106d2008cd063f48f6fe2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x97cc963e23ecd9b21bdea8cfebaa2b4d1a47caef4f6c404ce40849fc20ac2337116fab977fc21104491435e6301d499203d6d33b70e389ff42264891d422a79f76d20ec6d501af2d03fdf2d44d00894db77955b19a91823e665a4fe7cff2e9a7" - deposit_data_root: "0x4a985ca67543ab0f8e8510fc74fcd3efc4eb46e4590c60f415a568b4253701ad" - eth1_data: - deposit_root: "0x82c0b3acf41d802658accffa90b6991181cfe9ea6163104cffe745280e17831f" - deposit_count: "36" - block_hash: "0x3f9eb6bfe92c16a2bfd72bd6f5a7464255400c39a5ca46e0f50b91d3908fa0a8" - block_height: 37 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0x98b240f56117c7974d324f09c387e28e5acced1186003fe8df875e1c7377d7a5" - deposit_root: "0x82c0b3acf41d802658accffa90b6991181cfe9ea6163104cffe745280e17831f" - deposit_count: 36 - execution_block_hash: "0x3f9eb6bfe92c16a2bfd72bd6f5a7464255400c39a5ca46e0f50b91d3908fa0a8" - execution_block_height: 37 -- deposit_data: - pubkey: "0xaecc56f2b1c4011d450214d3e1254479d583a6a5c2c06fbc049512731f76227d140df9f36a3f76b4ccb4df1342403573" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb609cc73d220819290cb6f814295b01aeaa044a83105a7df712ab659e9368d3291b55f1b611bd28edc40c5694b5786a6068616edd2cdcac6104d0342b70b1aa0005bfb6828813d58fde482972f8c837d0650945b56e41ab157bbc4e1e7928351" - deposit_data_root: "0x6466d5d6da2e21242c13f72238a6796c78b52decddb11ed4e3b5e489b96317f0" - eth1_data: - deposit_root: "0x1cfd89cb2db15e0811b5a9cf5047e0e0b561afced194bb0a102a2ba155319600" - deposit_count: "37" - block_hash: "0xbd8cb6544b20968a1b580dcc4f70cfd241f736bb1c399063a7c25e077e734d15" - block_height: 38 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0x98b240f56117c7974d324f09c387e28e5acced1186003fe8df875e1c7377d7a5" - - "0x6466d5d6da2e21242c13f72238a6796c78b52decddb11ed4e3b5e489b96317f0" - deposit_root: "0x1cfd89cb2db15e0811b5a9cf5047e0e0b561afced194bb0a102a2ba155319600" - deposit_count: 37 - execution_block_hash: "0xbd8cb6544b20968a1b580dcc4f70cfd241f736bb1c399063a7c25e077e734d15" - execution_block_height: 38 -- deposit_data: - pubkey: "0x9243ef5ed3bd28892d1ef4f7aaf29faeb9c0e725673cd38e308bd756f20a9ee09de5cd9822e5e77bd03b734ef8a92695" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa19c19c9d1d616f7d3f57393d9ce5230dc2502f6764612a0911227811bea8688fb1f7ffcfb680ea21dd60a3a8faf596b0edaef49936901ae891d7f2266ef937731bb2fb34fd1e30bb161ab8b8e6ee389759519079ebdab9f1500fdb3b633d110" - deposit_data_root: "0xe430fb5af8b28239457fecf2ea0106ca1cf1e68e5d22ddc64adbb4b5167254be" - eth1_data: - deposit_root: "0x7580110cf5b8a5450a42ea097b2fcab6ad4cdb58a2100f8274e486b62ce673e2" - deposit_count: "38" - block_hash: "0xed447968de652c510595235a85ad711d39cb1d767bb247d94e82b63be69155ba" - block_height: 39 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0x98b240f56117c7974d324f09c387e28e5acced1186003fe8df875e1c7377d7a5" - - "0x5240648c484242305f1ff513c043647c24fc5e5d61cd4196ebf3c2c86402f1f0" - deposit_root: "0x7580110cf5b8a5450a42ea097b2fcab6ad4cdb58a2100f8274e486b62ce673e2" - deposit_count: 38 - execution_block_hash: "0xed447968de652c510595235a85ad711d39cb1d767bb247d94e82b63be69155ba" - execution_block_height: 39 -- deposit_data: - pubkey: "0x925b1fb57c06b5668567bd5aa196531032d6f8918dd4f702017c11b59288e3bdb98e3820ac22780f73580a4119de4bbc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x81c25d6edbe71803bd57c92c073be629bae323c28ff1a6f30ed5c507493d8271591b46581467cdd92f5847d20a1bc2760bc351838dbf690d90e06af696c7e9e46b943189b2aef29115e1c6e4ea83ec724c7955736e42c611859afebaf622a1a1" - deposit_data_root: "0xfa7f084e644997f1640e3568bd2400fa26cf9fd43e37275cc6578db139d9921a" - eth1_data: - deposit_root: "0xab72caf415667e76d99579f3cd9f5092c19470ce7e2267e9d7dc6d1c42c1401f" - deposit_count: "39" - block_hash: "0xd7ecbd540da29e0e617261e2b901815fc05c912a34d91df31de54e91eba6f49b" - block_height: 40 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0x98b240f56117c7974d324f09c387e28e5acced1186003fe8df875e1c7377d7a5" - - "0x5240648c484242305f1ff513c043647c24fc5e5d61cd4196ebf3c2c86402f1f0" - - "0xfa7f084e644997f1640e3568bd2400fa26cf9fd43e37275cc6578db139d9921a" - deposit_root: "0xab72caf415667e76d99579f3cd9f5092c19470ce7e2267e9d7dc6d1c42c1401f" - deposit_count: 39 - execution_block_hash: "0xd7ecbd540da29e0e617261e2b901815fc05c912a34d91df31de54e91eba6f49b" - execution_block_height: 40 -- deposit_data: - pubkey: "0x9648b83a4f09b4ca2021f0c193c5c41df1465715761bca52671ca790a3e92d67686b97b3d54c6110409779df887bd9c6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4a58b0ccdc9ba7bd7ac424e1e8b961ffc3839ce06f200551b7302a33d96a8c047f1184889c21711fc2de482c8a9ac05043ff957bee33046cdb77ec386225bf885776275e0643b155d528ba24ceab1c96e2c2da2d64021f7a26f742341f1c6f4" - deposit_data_root: "0x1edbb8a2956dd6fe52e14a004566f29757e212de7f64c8d98070f33ba78a78d6" - eth1_data: - deposit_root: "0x3c6fcce34339d0a97e566eb15d960c2b79440eb5a55a2a2e66dee1a3f63e515f" - deposit_count: "40" - block_hash: "0xfdb11831fe4c2fc8596455800b8420cce083f2ff92552d4255876432038a335c" - block_height: 41 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - deposit_root: "0x3c6fcce34339d0a97e566eb15d960c2b79440eb5a55a2a2e66dee1a3f63e515f" - deposit_count: 40 - execution_block_hash: "0xfdb11831fe4c2fc8596455800b8420cce083f2ff92552d4255876432038a335c" - execution_block_height: 41 -- deposit_data: - pubkey: "0xa34febc12af07316580b480364f90a76313ccce7927bbe263e27ea270853b02ad4d1428caf55363f3ebebac622cb9fd6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xadbf354b18eaaed15a3191ee9dc053d250fb54b59959eaba812b8a589797d5c310170da7b98b0448d4a3aaf3091305bc07b6c077b5973fcf0c7d5aefaacea5b6988b297f336841215a5bbed37f0271b325b178a4bf0835bfceae150ae6b3de37" - deposit_data_root: "0xcde3ed824c06263d6e165d50f2781de0f8e4b36f6e4167427a3e9b90ff45f7be" - eth1_data: - deposit_root: "0x3fdfcce325e69c97df3b47c43925e4ff58f633e9c1a4fcd6997c98a4d6931406" - deposit_count: "41" - block_hash: "0x4b93ca227181f17fc7c4f95ec672f19a1485d4ebc1981de852066119ede6f989" - block_height: 42 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - - "0xcde3ed824c06263d6e165d50f2781de0f8e4b36f6e4167427a3e9b90ff45f7be" - deposit_root: "0x3fdfcce325e69c97df3b47c43925e4ff58f633e9c1a4fcd6997c98a4d6931406" - deposit_count: 41 - execution_block_hash: "0x4b93ca227181f17fc7c4f95ec672f19a1485d4ebc1981de852066119ede6f989" - execution_block_height: 42 -- deposit_data: - pubkey: "0xb8cd1cef89aa1567a6058957442a698cf1b267130606f749451152959a5dfb50d243890d4adc2c3309f7696d54af1260" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x95031abea421c3997538c779d67489c04c635449f753f8836400532d70e6c4c7d0e42117b773da9b365dd9590cd01ad6079cdaa62e7ce21911bd519218064200e3d15ad252a072eac4554da519bb23f609b63684045b209933a5398606ba8aaa" - deposit_data_root: "0xdc998298cb82156f077de9a7289d8e92e3270653d4c5388f6ff11102dfa75a15" - eth1_data: - deposit_root: "0x60ad801fc6a68abf39ce5ee6db6913e019e9370ea744c3d4b7436f1bd1b7befe" - deposit_count: "42" - block_hash: "0xdd485eb08d480f2e25f7ae196f4eb69eaad495bf76be1c9c1d237f92ca6c0d40" - block_height: 43 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - - "0xe3897ec112ed55cfe802be2b7c9dfcc1b5a274736d0a8d8588d28b93bc2e26c9" - deposit_root: "0x60ad801fc6a68abf39ce5ee6db6913e019e9370ea744c3d4b7436f1bd1b7befe" - deposit_count: 42 - execution_block_hash: "0xdd485eb08d480f2e25f7ae196f4eb69eaad495bf76be1c9c1d237f92ca6c0d40" - execution_block_height: 43 -- deposit_data: - pubkey: "0x92a93728c252a45ef587ca53a037593912599d82e2b8aa1b734b99d500a0ac8c142092ea8b3c2c34a28dc8ddf337a249" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2efbb211e85d2a8cd924085571ab0637583a0fbe41a4f2f4e5b173631067f69fa0eebd2910673afe6bd59bb6f34fce60a974adacb5c1edf47bf8d81084a26e9cc16c553c1601594a393172613cdde131b96434801b6b77ca08f00db29294ecb" - deposit_data_root: "0xd038d18b6d28712d26bfefef79e3df36a9f5502e066f1a6667b0a0aca21322f6" - eth1_data: - deposit_root: "0xf82bbce9e12b06f122b0c9db7b2a24484f78772401aaf5056610de1071de7829" - deposit_count: "43" - block_hash: "0x65a8f07cef0dc9e98a8b3c685ed6a36bd6ad5fb6e97b57d202074839fd857e10" - block_height: 44 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - - "0xe3897ec112ed55cfe802be2b7c9dfcc1b5a274736d0a8d8588d28b93bc2e26c9" - - "0xd038d18b6d28712d26bfefef79e3df36a9f5502e066f1a6667b0a0aca21322f6" - deposit_root: "0xf82bbce9e12b06f122b0c9db7b2a24484f78772401aaf5056610de1071de7829" - deposit_count: 43 - execution_block_hash: "0x65a8f07cef0dc9e98a8b3c685ed6a36bd6ad5fb6e97b57d202074839fd857e10" - execution_block_height: 44 -- deposit_data: - pubkey: "0xb7ee0ef26144de04d9cc80864b869b7ecafbf1b7c0050403cc3c3b514368713b8bb708c464568a18c837e1fd21d09063" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa356193c75dfb2c15995650552d0ca9df6d9adc19dd19dcd26d79e88420fd0b1ad7a923e6c44ec5de81b5306b93f36040341b2393d40a3b0cd70f6f71dabac60bf2d1948a8e2da190316b56ebd84d9ecb7fd6d894380331070885da41c9b6277" - deposit_data_root: "0x433ac5e37040ef1005e56991ecc42a7207b1557a9d446e5b56a3bb8a617f52fb" - eth1_data: - deposit_root: "0x9d13195b240044e78fe982d51b31223feef1d8997e9f8231ac727a05343c55cc" - deposit_count: "44" - block_hash: "0x3615ed6ba3c2eac9dfb051aa571d6275395111b6eb80f82acd4da05cfd687f5b" - block_height: 45 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - - "0x7d41f8f8048c87f844b352ae9a9900997fe13c3cb8786346e55ba2c5affb00d2" - deposit_root: "0x9d13195b240044e78fe982d51b31223feef1d8997e9f8231ac727a05343c55cc" - deposit_count: 44 - execution_block_hash: "0x3615ed6ba3c2eac9dfb051aa571d6275395111b6eb80f82acd4da05cfd687f5b" - execution_block_height: 45 -- deposit_data: - pubkey: "0xafc0fa2ed6a270de6122a19d4600380b7f9b5e974d16f095f1702f55792ecab0128b155a69f17ad64a6de0a7063642ec" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c256c7242c520d646b3cb21d1fbf19097b8131857e7dc246fc81dfe8389b2335d9e1c2e394051a8293ec33bddb006c018696be5402901b5f9446d97cb9f8c5fe48c501e13382801e4e96dfae1df99471df39cc82741590c6915624f63d8bfaf" - deposit_data_root: "0x2522ea9a8f9dd6862e56af3f38827a80813082b521d43b7e21903c5b81fc1290" - eth1_data: - deposit_root: "0x1815da8f5d53ed18c0db0942e6e06bbc1e1e87cc21dd186b66d3a654fbb2aae4" - deposit_count: "45" - block_hash: "0x2ee3c9a0d2109d8544754a4d3771e393d9f15c55211c92be0f486e7478840805" - block_height: 46 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - - "0x7d41f8f8048c87f844b352ae9a9900997fe13c3cb8786346e55ba2c5affb00d2" - - "0x2522ea9a8f9dd6862e56af3f38827a80813082b521d43b7e21903c5b81fc1290" - deposit_root: "0x1815da8f5d53ed18c0db0942e6e06bbc1e1e87cc21dd186b66d3a654fbb2aae4" - deposit_count: 45 - execution_block_hash: "0x2ee3c9a0d2109d8544754a4d3771e393d9f15c55211c92be0f486e7478840805" - execution_block_height: 46 -- deposit_data: - pubkey: "0xa5869ba554d1432b09ee677c117511291b9901f169e870831f457caa6ccfab376cb1fe33813bdb495cf4afec9ea35fdf" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x91cd39a6714d4851e7927c03e5039fed3725573cc1e6005bb20ba21e47ee63ead1defd93963ad5905c6f8074b1197c131011daa8be473190c83523e3028db2f8dae5ff7daf1f708f2830b0ab70916a20154e8135a20972109cfcc1f76bb05caa" - deposit_data_root: "0xd716729370e1d97244f1758c3f5540e29461b31686c30dd333abbbae979e43a4" - eth1_data: - deposit_root: "0xda383b51baf43274f4bc3a1f6573a660a0f49feda6d3cfedf3aec0050a8bff02" - deposit_count: "46" - block_hash: "0xdc9667ffa9917e167974cac3e67dd0845657c81556dbf51e77b6915a2741bbb2" - block_height: 47 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - - "0x7d41f8f8048c87f844b352ae9a9900997fe13c3cb8786346e55ba2c5affb00d2" - - "0xbd3a8edc6db6b9fe322ca4af975e44750c273e56b1a6730a29adc004939ae9a8" - deposit_root: "0xda383b51baf43274f4bc3a1f6573a660a0f49feda6d3cfedf3aec0050a8bff02" - deposit_count: 46 - execution_block_hash: "0xdc9667ffa9917e167974cac3e67dd0845657c81556dbf51e77b6915a2741bbb2" - execution_block_height: 47 -- deposit_data: - pubkey: "0x92f43d79d9f488010b310a54f3fc2e7f4be191ca06d93e588c30c8abf59a52190e060b285ac626eb13cd95bbcc3a0a2a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x921608298683b15a514c05fb08f39958123498b48d8bbfad1fdd62fc84556aaba3b363e3a20c8423c9b8a157690241bb0ecfeeff3393f4502a2cd14cd8e2fa2373ddba64b573e1ec4c6941ba7cc423248878b9d46444ad8c10bd6f4bea2234e6" - deposit_data_root: "0xd5d100cccf8847fdf9b03e33e030d4a7dfc868e3c3b6107c4849bd78140bb87b" - eth1_data: - deposit_root: "0x62b97e8c2969a6f0d48a9f61a762561a8c7189af1c178b9215771e614909e7f8" - deposit_count: "47" - block_hash: "0x75bd1deec6fafccc3cf68485f19eba3c5e0edbeec29ae9f7c59b44f5c44a6f0b" - block_height: 48 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xd499ae9a9b57a50ee29e23a5f5130a7ae9dd46ac336b2741be8f300c7ff363e4" - - "0x7d41f8f8048c87f844b352ae9a9900997fe13c3cb8786346e55ba2c5affb00d2" - - "0xbd3a8edc6db6b9fe322ca4af975e44750c273e56b1a6730a29adc004939ae9a8" - - "0xd5d100cccf8847fdf9b03e33e030d4a7dfc868e3c3b6107c4849bd78140bb87b" - deposit_root: "0x62b97e8c2969a6f0d48a9f61a762561a8c7189af1c178b9215771e614909e7f8" - deposit_count: 47 - execution_block_hash: "0x75bd1deec6fafccc3cf68485f19eba3c5e0edbeec29ae9f7c59b44f5c44a6f0b" - execution_block_height: 48 -- deposit_data: - pubkey: "0x9698d9519a02b64f230e5a2520401799c2ca7d69ab23a6d9817943147264bf00d409264b928718245efff4f7ee97dd5c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xae3dcc94f04909fa6d3c8b56c6af1e037064eea73a5ee9300b2bb4655cd50e4e082108b3009c13d1deb4b94f7ff4c7cf028cb95f86c1e354892263d43826de017601f20c5fd99eadf42e1cacae7c2698715be3582d3cba2d2962891fb1dc58bd" - deposit_data_root: "0xbc47514e4e62c58f75742971af992d758dad75cf523eccbdaa33bee01a290794" - eth1_data: - deposit_root: "0xc36afe52bbbc41a91a58ee9e37deb448edd44992b00cb0bc8b5a32d46ce5dfff" - deposit_count: "48" - block_hash: "0x296842ff1c8cdd3e7729800191fe9a1da0209b1f554709867dadc6942510d3ab" - block_height: 49 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - deposit_root: "0xc36afe52bbbc41a91a58ee9e37deb448edd44992b00cb0bc8b5a32d46ce5dfff" - deposit_count: 48 - execution_block_hash: "0x296842ff1c8cdd3e7729800191fe9a1da0209b1f554709867dadc6942510d3ab" - execution_block_height: 49 -- deposit_data: - pubkey: "0xa852816b8e463178eea5acebb4b86d0acb6d8c6812cf313296bd271ea4d2fd89d281e5fc296df4df49019169bdf96922" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa562944e5476538903b89274c26eebf2bf87547968022140c4c950fcf9c02140ca064cdfa79aeb3bb0d0f738b2f56f401501fe791231633153ce1a56ccec8b8ba3b3d104b00b6477c89cf887b65d4ebc32c7f8d4d692288068941131d50c286" - deposit_data_root: "0x15e2e2026f0b8722f28569cf3752f02eec3d115c7a58947559fb0d40bc70f211" - eth1_data: - deposit_root: "0xe8659b62077801e4e285d99cb0f7df9eee6332af5d7173211c5119e89bfe98c7" - deposit_count: "49" - block_hash: "0x2ad547b4552b0d033398b10c76a224ea4e27af714a7d40208a1bcad524bd3de9" - block_height: 50 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0x15e2e2026f0b8722f28569cf3752f02eec3d115c7a58947559fb0d40bc70f211" - deposit_root: "0xe8659b62077801e4e285d99cb0f7df9eee6332af5d7173211c5119e89bfe98c7" - deposit_count: 49 - execution_block_hash: "0x2ad547b4552b0d033398b10c76a224ea4e27af714a7d40208a1bcad524bd3de9" - execution_block_height: 50 -- deposit_data: - pubkey: "0x8a298ee1ac0466ecaa04d5798048c6e192409af63217f32fd7e07794cfcdcd8deca055b9782dd1ad45a578a9ec10606c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa14f099ef773a2282a7c0d664557897c48aa594015e05373a5fef26c8dc848ae0c25718d3a9be982117159c908abcc1e0aab94110824c8410ea08039398a8569a844cafd9002ef319a2e6a0717a5b340a4d153e6f136be7a57ee5abd73cf7017" - deposit_data_root: "0xc46f9746cf62672d7d8ed56c05a7dc880f2475a6bd8a73151abef6f88907d53c" - eth1_data: - deposit_root: "0xc30f240a494e623caab8bc918828d31feea930902c6e3dca2c3a423f3f0bf102" - deposit_count: "50" - block_hash: "0xdcf96433eba5c4c5877afce8ecd730c943b0b3b02c6b36593fda5839cc095d7d" - block_height: 51 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xe74828c6edb30e3e4bf54de63aeab6c3f97f7d6f3a558705eaedef064b8645ce" - deposit_root: "0xc30f240a494e623caab8bc918828d31feea930902c6e3dca2c3a423f3f0bf102" - deposit_count: 50 - execution_block_hash: "0xdcf96433eba5c4c5877afce8ecd730c943b0b3b02c6b36593fda5839cc095d7d" - execution_block_height: 51 -- deposit_data: - pubkey: "0xae4d49364e4a36760cc74a675500055b9aed99bc19d31abb953ea156bb5a76dcf36769d15341b850114a30ffc8057780" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8fd33d2856c669b84efb3465006ba799fd3c680a56b104edc738107913fe0f91a6f99b7a69c337a493d8ea43791127211de5d964d1840fc403151a92d7b28cf8b141caa76807a06b673421effffa1d4e718ebe7fd27e59f1525126a01ab4bc4" - deposit_data_root: "0x9330e615cf2658bb9b5039e5c6eea90714a76b3b8f3759dff1e295c364f695db" - eth1_data: - deposit_root: "0x0e258dde53879f7f68ee782954ca2b25d222447991919c685f637409175252be" - deposit_count: "51" - block_hash: "0x9678505c7aacb038cfe8edc0a224c5eb2d1abfdd5775bc6d6260189c247e3443" - block_height: 52 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xe74828c6edb30e3e4bf54de63aeab6c3f97f7d6f3a558705eaedef064b8645ce" - - "0x9330e615cf2658bb9b5039e5c6eea90714a76b3b8f3759dff1e295c364f695db" - deposit_root: "0x0e258dde53879f7f68ee782954ca2b25d222447991919c685f637409175252be" - deposit_count: 51 - execution_block_hash: "0x9678505c7aacb038cfe8edc0a224c5eb2d1abfdd5775bc6d6260189c247e3443" - execution_block_height: 52 -- deposit_data: - pubkey: "0xb397692ccbf442bfe078174c85dbad7fd605e4ff1caf2904b31e4a4c79d6444813ad9b2093ac8fbd4dd59ec7a4c8c006" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa10cd137f49972e2e2a053abc96a1e356a4a65b9beac4b910a867c5e150e77a8b66cf7e32dae3ddcc1be9643a1ed775d0e6ab93def5f8685f756a014d6d6b721ce5b7d17cc85d8fefdf89276bbf67cf0b5775ef535bdea61a5fbe40b32d318ae" - deposit_data_root: "0x3170a57af6642be1e018dba7471b811acfb9ec1b9a66eddd3f28666e0001c492" - eth1_data: - deposit_root: "0x1b23c0123f7e5de30edcc56f34eef43f3dec1cee9374fc72a9f8d92b34111b1c" - deposit_count: "52" - block_hash: "0x2d4a16997b0e306d3e8f22dfd9b465ec6532273d54608973da7900e7223be426" - block_height: 53 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0x7d1f34ae9db53b9a029e24b8d766e0e54a464ac4eade9210f96eb71d77dbe9c0" - deposit_root: "0x1b23c0123f7e5de30edcc56f34eef43f3dec1cee9374fc72a9f8d92b34111b1c" - deposit_count: 52 - execution_block_hash: "0x2d4a16997b0e306d3e8f22dfd9b465ec6532273d54608973da7900e7223be426" - execution_block_height: 53 -- deposit_data: - pubkey: "0x87c9f7605d07550b46c79add5ea4e39de5014c03833669257bd6666b7ec838f53800104779940d8cdd884275a0f6a3ef" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb6a8b8d4ea3c51c1e53f1eb8eaca900db9c70a2c7c11f555856b6aff737c972c9f5871bbc8fb46d6ca3f33b271c8a7e20976ca805666b41bbe38a576167466db0f8beaa28995aba1ff250fa3e4f87a0843d3941ff477528dc7daad37f5a720a1" - deposit_data_root: "0x0c0f4d2950ee7d4b8062761154cfe0c1d6f13d9a4d5e2fef701ea38614add54b" - eth1_data: - deposit_root: "0x89aeb5a6e924afe40fa404ce4c31a09ad222c9f3dddfa1df9f4946ba81243754" - deposit_count: "53" - block_hash: "0x5279517c77c4fbf7e0c19cae59db917ff0ec55bc62ec2254df5cc945eb415daf" - block_height: 54 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0x7d1f34ae9db53b9a029e24b8d766e0e54a464ac4eade9210f96eb71d77dbe9c0" - - "0x0c0f4d2950ee7d4b8062761154cfe0c1d6f13d9a4d5e2fef701ea38614add54b" - deposit_root: "0x89aeb5a6e924afe40fa404ce4c31a09ad222c9f3dddfa1df9f4946ba81243754" - deposit_count: 53 - execution_block_hash: "0x5279517c77c4fbf7e0c19cae59db917ff0ec55bc62ec2254df5cc945eb415daf" - execution_block_height: 54 -- deposit_data: - pubkey: "0xb08f7feb86786c37661afb9951a959c9b465fd11ca98fcbc908fcf49144084051f6c363e2eb4459da2c2d03d84175692" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x86fb9f4c6ca3c5453646471e6393f1260f04f25bae62541203f6dfbfaf226dee57b0b6a1b2d698d3630a4ae418f7986b0273299006e4488f835612311eccb1fecaf576e1740d29b0c26478ec11eaf644b3e3c696ebe562e6942d282bef29f82b" - deposit_data_root: "0x04e2ccad9538abe82e053c1414dcd8083d20206e84a3ce294036e271bf391e7a" - eth1_data: - deposit_root: "0xd749a15bc781e795b4e88adce9dbd068f0350586391f8170e025a4a671c7be06" - deposit_count: "54" - block_hash: "0x7a5b1937cf1cf319c059b24ac4d7a905d841d3c725da9597f32cd609159f1e04" - block_height: 55 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0x7d1f34ae9db53b9a029e24b8d766e0e54a464ac4eade9210f96eb71d77dbe9c0" - - "0x45ae70157fb3f9a80f56cedf94dd1211a7bb8fe79527c7077097c766dc4fcc92" - deposit_root: "0xd749a15bc781e795b4e88adce9dbd068f0350586391f8170e025a4a671c7be06" - deposit_count: 54 - execution_block_hash: "0x7a5b1937cf1cf319c059b24ac4d7a905d841d3c725da9597f32cd609159f1e04" - execution_block_height: 55 -- deposit_data: - pubkey: "0xa48cc260df1df875176cb17493a5b53d669c091da74d5075acb8952a641b1b7ef68d01f009c1a365d2fa80937c79dd6b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb3c12fa96e2938a28f872bdf1e62a037dcbe3d5fb0e7cee58bcd2d2c6d82ffdc6428a5ec997a8ae26d113f30959fddb407f319ef49f989fdee215aa96d2fcf30b6154590ee9f856daaccd20c344fa5d4b4cb6678a3409aa619af35bed6f5259e" - deposit_data_root: "0xfefb7de90e7a23e87b7df79ef2b97df376a89aab301014112ce01f2362515d7e" - eth1_data: - deposit_root: "0x92e91d9cb729330a946dea613ffd50ed1911c3b7cdb92a7e400d486310a77c6a" - deposit_count: "55" - block_hash: "0xfb4bb38d9560af55e012e04dc652fe291826bbd4dd8fdc350300f9d97e463ec1" - block_height: 56 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0x7d1f34ae9db53b9a029e24b8d766e0e54a464ac4eade9210f96eb71d77dbe9c0" - - "0x45ae70157fb3f9a80f56cedf94dd1211a7bb8fe79527c7077097c766dc4fcc92" - - "0xfefb7de90e7a23e87b7df79ef2b97df376a89aab301014112ce01f2362515d7e" - deposit_root: "0x92e91d9cb729330a946dea613ffd50ed1911c3b7cdb92a7e400d486310a77c6a" - deposit_count: 55 - execution_block_hash: "0xfb4bb38d9560af55e012e04dc652fe291826bbd4dd8fdc350300f9d97e463ec1" - execution_block_height: 56 -- deposit_data: - pubkey: "0xac9f4df3f20a16a9fefad08817fcbc9a6ee17f7512db006414b4aa6f234c2313585ef72c5776df55fa6284af4bc3f631" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa5d0be0b159b75f2524e1bc1243bb8f1272716daa2a1c635f0b34004b30d9038450cbd9b72f29a17d1fb4d99857fdbd710e2edb2036aa89589d1344b6e3984fbaa44ee0409fa048d0dd86aa1aa0164663b187e0ec9f798b490dc4b34b095152f" - deposit_data_root: "0x5568cb23722d865d414baf5f7ee92234383e175fb3e5b7cae390244f9735987b" - eth1_data: - deposit_root: "0x565ce384ec4ca0d45ab8c20b895996f4dd15f01f00497b24cdaf6378602efed7" - deposit_count: "56" - block_hash: "0x13a0c8bcd47c1e4d208515dfb6e9773ee2d5f894691e7986bb8d8699f33d9495" - block_height: 57 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - deposit_root: "0x565ce384ec4ca0d45ab8c20b895996f4dd15f01f00497b24cdaf6378602efed7" - deposit_count: 56 - execution_block_hash: "0x13a0c8bcd47c1e4d208515dfb6e9773ee2d5f894691e7986bb8d8699f33d9495" - execution_block_height: 57 -- deposit_data: - pubkey: "0x94f0c8535601596eb2165adb28ebe495891a3e4ea77ef501e7790cccb281827d377a5a8d4c200e3595d3f38f8633b480" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x87d4271630df8e3780900e5171d3642a781205154f11be812e923c87d19a3224213b9dad62ed42f1126a0b52157afa9e16acfecf96c7f8c2345bc1f744e491136cb100d82f549467f595fa6393ff0f8d0dd83cf5c95d88c47d0023bf0796fd6f" - deposit_data_root: "0x6adfa7ccc8b757b108c02b37ee27bc04e83e9c7538b1c07ebcf342d564c56135" - eth1_data: - deposit_root: "0x352eee8320a9c83dc4f9b5086b92df7ab3d1844f77cde7f3c3daf8905d91c4d0" - deposit_count: "57" - block_hash: "0x1023685ab1df034f7762e46035d995d87f972c29ffaa097a5b6301da756398cf" - block_height: 58 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - - "0x6adfa7ccc8b757b108c02b37ee27bc04e83e9c7538b1c07ebcf342d564c56135" - deposit_root: "0x352eee8320a9c83dc4f9b5086b92df7ab3d1844f77cde7f3c3daf8905d91c4d0" - deposit_count: 57 - execution_block_hash: "0x1023685ab1df034f7762e46035d995d87f972c29ffaa097a5b6301da756398cf" - execution_block_height: 58 -- deposit_data: - pubkey: "0xb5bb0162a4f27d1bab4c7dc3d20f5a75d6ee98c56bcd309a1f0f307685ad47ffb8a35bfdf8431b9b954b59662a74c478" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb091cd5476b2bceec9f6cba38b3787fb080d9ea63cd6ed19eebd72be7ab0887e44f233e993b9ded4fe33353a9aba5a9206b8a9da387d222e58bf05cf333b25f6559557cf95645c785c75683317a1c97dec807770e57d581107e9fbca03069eff" - deposit_data_root: "0x9fad5a196b6ac7c47a1fd191fe4eac56bd9b9fed5d275ad0311e165059d7d40e" - eth1_data: - deposit_root: "0x349bbbc8e21e10251fcfceec3821ea9dd2806526e71fe472bb8b84d8d5458f5f" - deposit_count: "58" - block_hash: "0xdeaa8a27fbc74fcbda0b7a30da9c4e4392a9f961b90c45a272a204bbfb5d8d4e" - block_height: 59 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - - "0x2ef9c25463a9664b369b20321671d2fec217ea4c64ee3a714e5983a0be95acd0" - deposit_root: "0x349bbbc8e21e10251fcfceec3821ea9dd2806526e71fe472bb8b84d8d5458f5f" - deposit_count: 58 - execution_block_hash: "0xdeaa8a27fbc74fcbda0b7a30da9c4e4392a9f961b90c45a272a204bbfb5d8d4e" - execution_block_height: 59 -- deposit_data: - pubkey: "0x8826e820179fd321819e78ffee16f50ac528db2da71ad8c269f60b878bc4887c79c0545b3d750e86e490d5ba9083cb70" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x95622cabae0c3d62f58fba1302e207d5ce92aa3df653968f020dce42b84d49ac928f7ebce159b2b5fa55115bcd0895820269879dd4ebea6359d5c9d8b9c209cac6891552985da1e64a60e6b0959b2bf0ed81d97381ef9b1e0aa59217e9dbd280" - deposit_data_root: "0x80ecb93497e9a21c4e6d48528adc8d32ec4808a275fcdb2ecab6fc052e6936a3" - eth1_data: - deposit_root: "0x56f2b33a48de69dc2f0652bb1d6d4a63de86b9db2319b51f59f93225d93787e8" - deposit_count: "59" - block_hash: "0xecfb4b768d48d31ac3d30f640d00d0afb9c932ba7316c9f21ede2ece7e4a65c1" - block_height: 60 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - - "0x2ef9c25463a9664b369b20321671d2fec217ea4c64ee3a714e5983a0be95acd0" - - "0x80ecb93497e9a21c4e6d48528adc8d32ec4808a275fcdb2ecab6fc052e6936a3" - deposit_root: "0x56f2b33a48de69dc2f0652bb1d6d4a63de86b9db2319b51f59f93225d93787e8" - deposit_count: 59 - execution_block_hash: "0xecfb4b768d48d31ac3d30f640d00d0afb9c932ba7316c9f21ede2ece7e4a65c1" - execution_block_height: 60 -- deposit_data: - pubkey: "0x92977e71396633d442f61e16a0cfcf8ffad0af93c9f1b7fdf4f7ccb816de052925fc192922d6252d325ef9fa2e0595d2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8645569a7d2c3a844a0215c163fb2e247b4201161e2399f213c0985b568ac98961398f0c712c05892857cddf384a12e007ad9412dbbc26b18b7e76b9a5d5735db8dd0142e879b7b46f8d42dc05be19ec9cc9bd42b741cffbbc250d576f86232f" - deposit_data_root: "0xb4fc5adc460335413f7ddc9f38007ca12c841212a86961b3190b566e371b6fcd" - eth1_data: - deposit_root: "0x2badea58e40583186b7ad89ed366eb9fe85cd849309ca43bf5f4703b45c7c9bd" - deposit_count: "60" - block_hash: "0xb6a0eed143a5b2bd2ccace6a5f28ec0b9ef819eb82a781b12f82ff9e717077c7" - block_height: 61 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - - "0xc7acab11e9b20b4a85bafee152a8fc5314df74b63c324dd84a917f89c3c5bf0a" - deposit_root: "0x2badea58e40583186b7ad89ed366eb9fe85cd849309ca43bf5f4703b45c7c9bd" - deposit_count: 60 - execution_block_hash: "0xb6a0eed143a5b2bd2ccace6a5f28ec0b9ef819eb82a781b12f82ff9e717077c7" - execution_block_height: 61 -- deposit_data: - pubkey: "0x91ae4686b0d20470409f020eaca826c3efc6c1926ed25d05e6f0f7916391ec89c2341917277c437ac8fffffe94b68111" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa88873293cce3d3e180c98a6e973057873578ee51480e5d2a821c6137aa143ab8ccf64dcb131013b6203fdbbbb848efe015949a431ff7b401b131ab6d33ce9dcc698c00e3503c5cd98d935d2040952dc9a863fa7e8ff60e52088ae198773b740" - deposit_data_root: "0x75dac1ed3a104cdfa3e93c4b91e85ff6bd7a309a3f881fedf48219657ba71993" - eth1_data: - deposit_root: "0x243b61a30048fd9847aa0c1424feffee5876462556952e6120800137cdfca8cc" - deposit_count: "61" - block_hash: "0x3febb45aaa3b77bf4190c51f349ae02ab28df9c090a727294f2d6850feaf71fd" - block_height: 62 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - - "0xc7acab11e9b20b4a85bafee152a8fc5314df74b63c324dd84a917f89c3c5bf0a" - - "0x75dac1ed3a104cdfa3e93c4b91e85ff6bd7a309a3f881fedf48219657ba71993" - deposit_root: "0x243b61a30048fd9847aa0c1424feffee5876462556952e6120800137cdfca8cc" - deposit_count: 61 - execution_block_hash: "0x3febb45aaa3b77bf4190c51f349ae02ab28df9c090a727294f2d6850feaf71fd" - execution_block_height: 62 -- deposit_data: - pubkey: "0x8a0d241955104bedacb3b829162f2b457915c2beb9018ede8ef8ea80f401b471c42354358da9e62b51c38d54263a78a9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa073c1da30c9555dde136f86879936cbf247828ad39b688058b8dad95781651379dfce772fb5aa93d60e50d0d2374ada00e3ebc42e2b1d732512f990dafa34dc7dd1553bff38a171b06f0b215f40bcf5fb354d914bb595acb8dc49d042896ff7" - deposit_data_root: "0x3274fb1171cb037b6242c298f1680eaf23f01ef807669e9b00f65eafe0640a1a" - eth1_data: - deposit_root: "0x52e5a1f96b0f8c69423bfbeb7ed3888cfb9c9098eafd0eeb14a9a8c21d77d245" - deposit_count: "62" - block_hash: "0x8fbc1f4ff163d89df6143c66f1df08196d0313df09ff0b062925ec33e48415bf" - block_height: 63 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - - "0xc7acab11e9b20b4a85bafee152a8fc5314df74b63c324dd84a917f89c3c5bf0a" - - "0x997db7123f404f063da7db4a2d3952d11f30820ec8445b5da03eccfde69e946f" - deposit_root: "0x52e5a1f96b0f8c69423bfbeb7ed3888cfb9c9098eafd0eeb14a9a8c21d77d245" - deposit_count: 62 - execution_block_hash: "0x8fbc1f4ff163d89df6143c66f1df08196d0313df09ff0b062925ec33e48415bf" - execution_block_height: 63 -- deposit_data: - pubkey: "0x80a2be2c7dbce8ddc2eba03522697587c375a5a9e92d4b31ed9e3c34bee047095d93e3c70b1662b3faa301f5b19978e5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x81e2f7ffe007e2d6b121ec976d7a9d3cd053d82cf07f2411184522664755306185dbb3352c5bd7e83154fafb34a56f2601c164f280e447e64655ba7e9457f5967f15eb4d6080af7ef971b9067a498266fb3afd979a251169402cb4d80205b9e0" - deposit_data_root: "0x75169d67108dcd4c68492847c39c0f7b24ecbb2a0240e62dd5f72518e142df11" - eth1_data: - deposit_root: "0x8ffd6666bccbb0269e2df5e53b91844c9bdbac57d3089d6d9a4d91c1963a61c9" - deposit_count: "63" - block_hash: "0x4d7818aafb46a6f055359d6d7c664c3b36599a2dcab8cf332ff896345bc0cab1" - block_height: 64 - snapshot: - finalized: - - "0x378775a69829a0b8467bf331f4815d2ab91c796f5eb14cd9c08347421a245f2f" - - "0xdb1b9444d0f397f37f74bfcd3cc45beb343c4fe728174c624eaeb3647745b17b" - - "0xcc3b81ef99612124c28a53fdad6aef205b53c82843c080b9f269b80313fe7481" - - "0xc7acab11e9b20b4a85bafee152a8fc5314df74b63c324dd84a917f89c3c5bf0a" - - "0x997db7123f404f063da7db4a2d3952d11f30820ec8445b5da03eccfde69e946f" - - "0x75169d67108dcd4c68492847c39c0f7b24ecbb2a0240e62dd5f72518e142df11" - deposit_root: "0x8ffd6666bccbb0269e2df5e53b91844c9bdbac57d3089d6d9a4d91c1963a61c9" - deposit_count: 63 - execution_block_hash: "0x4d7818aafb46a6f055359d6d7c664c3b36599a2dcab8cf332ff896345bc0cab1" - execution_block_height: 64 -- deposit_data: - pubkey: "0x86a73886aa0114bbdbba346cb7c07376c81b549a4802c24d98ebbc54a6a1b5d2ac874ef657cfb27c3644fcb85f97a2b5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb697a8a9e4d724302efb08e88ec4308b803fe8544cd5d53324dce4b601a18a66fd612318734d520d5909051f62e9e9e90823a7bcb951c2b5b9d349f0c5b57ff748ec93a1750df96e212f39900eac541d0753683d46a375a421300c0434c3fa88" - deposit_data_root: "0x1418de3c263fa52285b93a809f16134cf4e008bebd2d81da7eb7d6f2a22c61a2" - eth1_data: - deposit_root: "0x5c7e3d0e9ac6cd2613e89748ec650d1ec5d44bf936c7761a0f5fc9526f263e17" - deposit_count: "64" - block_hash: "0xab873b4514bca399da91a00dc630df65bf37f53517b54c075f2f40a01e47661c" - block_height: 65 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - deposit_root: "0x5c7e3d0e9ac6cd2613e89748ec650d1ec5d44bf936c7761a0f5fc9526f263e17" - deposit_count: 64 - execution_block_hash: "0xab873b4514bca399da91a00dc630df65bf37f53517b54c075f2f40a01e47661c" - execution_block_height: 65 -- deposit_data: - pubkey: "0xa98c264dfc3bc3ed635df5dbfd54909e77600cd68480ec201d9f5c416580591daaa9735b04743e10e7fc6370a8189775" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa946b360375c6dea0c890263ccdefeb4f4a815bea7ebb6513de80f7932fb34ed36f3d5e41f26a9ef1550531d7ae807cc1094b7b5cdf2682ca713658ee8ffe4de45a0a20d8ea3a10b141e4d831030339e78936f04363529be77fcee46d4a9642a" - deposit_data_root: "0xb484b20a0bc7eb410b062c857ecce9460c3aa86b2b81133ef727507bb60c6f9e" - eth1_data: - deposit_root: "0x5744f4e817aaa5221b70a045adde9f74069f40880a4646630c0417afc759a267" - deposit_count: "65" - block_hash: "0x9f358a6ec47032ba2fd6e11f79da1ef89cde8d1cdfdc3f8f21006fc056a1ad61" - block_height: 66 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xb484b20a0bc7eb410b062c857ecce9460c3aa86b2b81133ef727507bb60c6f9e" - deposit_root: "0x5744f4e817aaa5221b70a045adde9f74069f40880a4646630c0417afc759a267" - deposit_count: 65 - execution_block_hash: "0x9f358a6ec47032ba2fd6e11f79da1ef89cde8d1cdfdc3f8f21006fc056a1ad61" - execution_block_height: 66 -- deposit_data: - pubkey: "0x8bb7aa61aa8bbd2b7825d28c340da89b625381232dcf2742276b4e3a2e4a0f42ef68794fdf005d94014636732fba2f40" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8adda613ebf39f91333958b18dfb271b5b75d97342a9447e5371e31af7fdbec354687868cbf1f2e57a71c3aee43e623f0a6161ff94eebb064cf57aec311e0f638b24b864c1433622b5236e5d33e410626c34784e8409a90bcf855192fc1ae0b5" - deposit_data_root: "0xebac12ca39e81dcea9e7c2de29be0ffed4b4b560c5cf4539f3c3d7789ddf03df" - eth1_data: - deposit_root: "0xce3f915b2665672a9e96e35249fd4a07a0ff010b8f455995fb4d9d4d383d5921" - deposit_count: "66" - block_hash: "0x4fe81ed058f82fd3308a29454b21d44d977963ab016f3aa161803a909fe20768" - block_height: 67 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xb74da2b05b56d5a84748b4616151668e0b0bf38c880acdbe71451e62bd7306f9" - deposit_root: "0xce3f915b2665672a9e96e35249fd4a07a0ff010b8f455995fb4d9d4d383d5921" - deposit_count: 66 - execution_block_hash: "0x4fe81ed058f82fd3308a29454b21d44d977963ab016f3aa161803a909fe20768" - execution_block_height: 67 -- deposit_data: - pubkey: "0x8bb9e1693eab1496d7583bf22fb1f2a475934c63b4d94118940617aa187bc277f738223e0ec1ce8a5566035d9bcc5470" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb00b5a95db40dd1b1bdbd364519b03447ed232d3c88dfad68203892523509831131f21b819260be2554d0e627f113f831477f83c9fc1948a451f4dab078c05376608168964fda9b8d033f8768f008fa1db2d0c61c47a9adb3812ef5e25f42889" - deposit_data_root: "0xedbc4b624f488b096f5cce8386dc735682a0d890193dddc47273a3f8e9587c4f" - eth1_data: - deposit_root: "0x8039f6582e426da6ad47fa64f2b0c5927b11f9980914d12174d0d4c883ab1c21" - deposit_count: "67" - block_hash: "0x3dbbf6de20c3b31947160ee99b05fca4cb77b35ef72f7bd5ef183b57dde0f4bd" - block_height: 68 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xb74da2b05b56d5a84748b4616151668e0b0bf38c880acdbe71451e62bd7306f9" - - "0xedbc4b624f488b096f5cce8386dc735682a0d890193dddc47273a3f8e9587c4f" - deposit_root: "0x8039f6582e426da6ad47fa64f2b0c5927b11f9980914d12174d0d4c883ab1c21" - deposit_count: 67 - execution_block_hash: "0x3dbbf6de20c3b31947160ee99b05fca4cb77b35ef72f7bd5ef183b57dde0f4bd" - execution_block_height: 68 -- deposit_data: - pubkey: "0xafe6eface52fb6de91055a81abf9aa6e42ce2ef36fd8ae0d09aec6e5d8bd40a065dfccda6104af94df3f7a5854559ef4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x966e020128302f799fa32f954776a47b6e40fbf346c8055427254918a88c39609ead584b770a03a4425dfcf57fd79aff17f0ac1e364a383823fef8a7aaa06d3a252c6c8843ab9bd156bbb5ce8ecb542c5459c9d587e5e29e90f640c8fb1a16bb" - deposit_data_root: "0x678f9a3c3a59f9118d5f278fefa238950ea26786faaf42d3d17096c81c89a78e" - eth1_data: - deposit_root: "0x4e422cc0aaad3a3a9f4e415a68f1286df79ea6a95326f73b6822bad8eeae6508" - deposit_count: "68" - block_hash: "0x64bb0a98a090f4763a6709b870b3366512db0c089547e1d2bfdefb531e7d08ba" - block_height: 69 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x59fc60d715f9f381f553928b5bab510c59f2b5763bc27115827930abc8fa06e1" - deposit_root: "0x4e422cc0aaad3a3a9f4e415a68f1286df79ea6a95326f73b6822bad8eeae6508" - deposit_count: 68 - execution_block_hash: "0x64bb0a98a090f4763a6709b870b3366512db0c089547e1d2bfdefb531e7d08ba" - execution_block_height: 69 -- deposit_data: - pubkey: "0xaa241b2afbb33f92a5d281aec9c8bac8997c1dddc051455fc0f334de48320f160b5029b552495aed21ed9ce252aab499" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96e8e6ad4c9339471b4133f16fa80c48183a4bb54ae9f7097766112005375693dcbc459050d4a20a88a4d72601ea171402dbb3b934fecfc4e90c8aa8d9a20818086b126f0c72bf8eff58008aabec8834f8b252660289b818c1724b8fe6f75fbb" - deposit_data_root: "0x27dfc1b46bf479c37bdfd8fdd34ab1f760dd118b031edd608a5b09e217f67bc1" - eth1_data: - deposit_root: "0x1878ee9b4d268fd60d60bbfa65e807f1e45331dfb13dcacc489de9dfc27f4a23" - deposit_count: "69" - block_hash: "0x899a35f3d719b2eb46085aea5820429a4ea164cc9092f096418df37fc8d5b3c4" - block_height: 70 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x59fc60d715f9f381f553928b5bab510c59f2b5763bc27115827930abc8fa06e1" - - "0x27dfc1b46bf479c37bdfd8fdd34ab1f760dd118b031edd608a5b09e217f67bc1" - deposit_root: "0x1878ee9b4d268fd60d60bbfa65e807f1e45331dfb13dcacc489de9dfc27f4a23" - deposit_count: 69 - execution_block_hash: "0x899a35f3d719b2eb46085aea5820429a4ea164cc9092f096418df37fc8d5b3c4" - execution_block_height: 70 -- deposit_data: - pubkey: "0x974b2aed17665e51c1c091998ca9649875330947de3d2733a5bd2eda69b0c593cdac2e416993a87f9a17aec1ccdc2368" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9695b7d689cb75c10edc7a6e7b9e03b802ed7e9256d589a7f5f2d192454a4b58000972109d812ca067b62caace91281714a783bcd8233414a5ca9abd3ff3e8b59d51b383079a61e3a5f9529f279d9e1f4fd818ad3daea046e883a9c0bbdd1b6c" - deposit_data_root: "0xa827f1f12751ca75d95a814694177e948d61bb11d675d3730f8cdc8889329c71" - eth1_data: - deposit_root: "0x93fe27d7e2eb9366225f56a794563f4dfcf3696253dc58eb25eb54d0c0eb9def" - deposit_count: "70" - block_hash: "0x1ba29709a4d5765dd8ffdee19aa7407fd25c7173a3f71e5c7920f3f5ca941b49" - block_height: 71 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x59fc60d715f9f381f553928b5bab510c59f2b5763bc27115827930abc8fa06e1" - - "0xee50b240458527c727db7672bbeebac6a3d40f017fabafb80c39bc12ad50df21" - deposit_root: "0x93fe27d7e2eb9366225f56a794563f4dfcf3696253dc58eb25eb54d0c0eb9def" - deposit_count: 70 - execution_block_hash: "0x1ba29709a4d5765dd8ffdee19aa7407fd25c7173a3f71e5c7920f3f5ca941b49" - execution_block_height: 71 -- deposit_data: - pubkey: "0xa3177a98f653cea646f525f0f13348efb27e0d3d0cd824704c91d8d959096d259c9e577298f444acc629920c9619be50" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9985fe391e73d35c8044754de668b3113d8c341bcb04acbd6eddf8b683ea0fe5dc163b434243cb6ff5588a2d4146b1151851bd39edfe532a31a3b81afc644f3ae781ff2f6e73bca955fe47fa4029f8759ab2a6784e1eade8f34c97223bab6af2" - deposit_data_root: "0x679b8078ff958c2a06ded461b6891f5a8038dc16adc475ba5867998a7d14adfa" - eth1_data: - deposit_root: "0x5173afe39bf20d7f26ab0d805059f50e0024f037cbc446e12171978d8d87e863" - deposit_count: "71" - block_hash: "0x7ed23cf2347fbaf8084d0c9b4acd2a88a5b793a68c7dd50d00627d4d6ad91457" - block_height: 72 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x59fc60d715f9f381f553928b5bab510c59f2b5763bc27115827930abc8fa06e1" - - "0xee50b240458527c727db7672bbeebac6a3d40f017fabafb80c39bc12ad50df21" - - "0x679b8078ff958c2a06ded461b6891f5a8038dc16adc475ba5867998a7d14adfa" - deposit_root: "0x5173afe39bf20d7f26ab0d805059f50e0024f037cbc446e12171978d8d87e863" - deposit_count: 71 - execution_block_hash: "0x7ed23cf2347fbaf8084d0c9b4acd2a88a5b793a68c7dd50d00627d4d6ad91457" - execution_block_height: 72 -- deposit_data: - pubkey: "0xa8a18565733e70663c77bc0c80e08f50de908cc048152f1e7dae85d8cc218afbdd337d7d33a44e25400be2f06907c64a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x905cb27fd1bed206b261cb543ffd3fbed57f5b9d938c4dc488e1bd34d198395bbaf1237cf183cc73f5c8e2e31d20d4c40e191278661c85adfb507024b5e9a5dc3ebbed92e8aaa85c0cfc6df6b05375c26b2f49a8f695ac156bff342bf16fc4cc" - deposit_data_root: "0x8bc78f4a029e716370a431e7034b9fa1991e36246d00fbf41a1dfeaba5748f67" - eth1_data: - deposit_root: "0xd09cddcb5675f6c5affff8a42f1fe425994bbe9133443d3f5516f8b34a9d0ba9" - deposit_count: "72" - block_hash: "0x16925bb1988c358148331ce34f247a985c829d8c14bed9481998942083cfe783" - block_height: 73 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - deposit_root: "0xd09cddcb5675f6c5affff8a42f1fe425994bbe9133443d3f5516f8b34a9d0ba9" - deposit_count: 72 - execution_block_hash: "0x16925bb1988c358148331ce34f247a985c829d8c14bed9481998942083cfe783" - execution_block_height: 73 -- deposit_data: - pubkey: "0x902ff56a7a4c5b6cc57708ea7b0b72cb54e4b821c95373f503648185f15208f6ca6281677fa0ecc14f911d7b7ca04f4e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x84b716bac859e61b80e0cfd3fc484d59c2865794d11e3264d58d6c4d2af2e2eef12a94944f331a4b3c83221a5d1fba2e10fa236515db3d9eaf92c4ff9c118cf73f0cb59b4c7b14f253e7c7b7b2cd0d23f659c24bd4f37a99ac82bed18c56e8cb" - deposit_data_root: "0xa23bbf261e1427b5532adfeba387f89d35f66f776af44be7f183d06af0e55828" - eth1_data: - deposit_root: "0x44c70c125786b9ab6f938ecab3227d21da2ed26f1a1677f8f78e4058dc721403" - deposit_count: "73" - block_hash: "0x869606a66fc329f9076cc7257a61018e04b6a6c658298c65de81f67bf6990a4c" - block_height: 74 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - - "0xa23bbf261e1427b5532adfeba387f89d35f66f776af44be7f183d06af0e55828" - deposit_root: "0x44c70c125786b9ab6f938ecab3227d21da2ed26f1a1677f8f78e4058dc721403" - deposit_count: 73 - execution_block_hash: "0x869606a66fc329f9076cc7257a61018e04b6a6c658298c65de81f67bf6990a4c" - execution_block_height: 74 -- deposit_data: - pubkey: "0x98f011f9a4dff94eb0352ff6e21b7df45e2a112bd5d789b5729111b89b368e7ed554e4d1c16b72f4d105090173cafed2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89641c38182c333a0485aeb0c4eb6f399a3581d19b33370522623d4c827de1cd5dc79c510a7e5bcd33f4d7a12bc6ef101630d2e4bfdf28c89b974c3927c314d118a345a3c424d0b3680c2de454e0e95f1d9c96565c2a7d771af7e8baddd25f02" - deposit_data_root: "0x65569e8f6db92744dd0abfbbc30a083986f842aa23cc02408099f2b586a3ed1d" - eth1_data: - deposit_root: "0x727ba31c7148e93fea338887cb4cc053a8eaa60dbd3dfc15607e837a845b38e8" - deposit_count: "74" - block_hash: "0xa68c064f8ee59d1857ae8a3a8eea5b59bce083fcde164fed763ac2ab30e51f90" - block_height: 75 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - - "0x8b4eb1bbcf79beaf6350d27870239dac66f0155fb7f8807a1f2b96afbba18f03" - deposit_root: "0x727ba31c7148e93fea338887cb4cc053a8eaa60dbd3dfc15607e837a845b38e8" - deposit_count: 74 - execution_block_hash: "0xa68c064f8ee59d1857ae8a3a8eea5b59bce083fcde164fed763ac2ab30e51f90" - execution_block_height: 75 -- deposit_data: - pubkey: "0xabef42538a17a55804b634aac9d211b92b5768c4cc1263342ca287323bb3d5c768080451d1b5d652e9f8646fbb35f57c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa7bc665e4424cf5e48e6b1dcc5f9d85e9f44ccb12690969a548fc0835b2fcdb6d587c997696e19fdc16ecb8c9b8a9d4810795c716232c2a2839845cabb70ff78aedab16d3ae55751c4b75a64dcbd5b630b652308300313457176c99889436d94" - deposit_data_root: "0x2da83bfca1b961cbe477157ccb844db21392e1fb44822b1b330c686984e85586" - eth1_data: - deposit_root: "0xc7a1f21839a10b008b4503cfd0e8f57dc67b83642e8ed9aa1d9f042821f22b01" - deposit_count: "75" - block_hash: "0xf7b4c7f12fe8c83d3546300a2bbf4a01034fc10eaf79295db8dc9e1b36c9a20e" - block_height: 76 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - - "0x8b4eb1bbcf79beaf6350d27870239dac66f0155fb7f8807a1f2b96afbba18f03" - - "0x2da83bfca1b961cbe477157ccb844db21392e1fb44822b1b330c686984e85586" - deposit_root: "0xc7a1f21839a10b008b4503cfd0e8f57dc67b83642e8ed9aa1d9f042821f22b01" - deposit_count: 75 - execution_block_hash: "0xf7b4c7f12fe8c83d3546300a2bbf4a01034fc10eaf79295db8dc9e1b36c9a20e" - execution_block_height: 76 -- deposit_data: - pubkey: "0xa8e3c2d3ac4e0e3c83380577ff7b7b5b2a98571e0d04ddebc0a6c472ce3bc5cc6a6733be728a0ee17da74b7691d2679d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2719b1ed9259b55d519158388e8cfd749a186c6247c2a637848e133d761ad6b105e27d3f1a950455d8abaaccb756e6016c838c3ed0abbb9a0bc966ef6f58e681d232cedd792c2b19cb3d17a5240a16b97c62ce29682c078131b67143878d02f" - deposit_data_root: "0xbd2182e7a29009df9034c125e8e09f7f6e3b862944aab607f84d64545ab002e4" - eth1_data: - deposit_root: "0xee5ee677dd7394ae7c9548383112262e9af48b1f84a109a1e42ca1a1fc57e527" - deposit_count: "76" - block_hash: "0xdd2ed41e4e3336c4afb390d9c8572db1a2d5d403b470eb88a3a85df3bbbd107f" - block_height: 77 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - - "0xee021dc6246d96e341b4df28f5db26a62e2d9716eb92394f5bd7fa7e246fa85e" - deposit_root: "0xee5ee677dd7394ae7c9548383112262e9af48b1f84a109a1e42ca1a1fc57e527" - deposit_count: 76 - execution_block_hash: "0xdd2ed41e4e3336c4afb390d9c8572db1a2d5d403b470eb88a3a85df3bbbd107f" - execution_block_height: 77 -- deposit_data: - pubkey: "0x98f620aadc4e58392b5b583fed96c452b54c39ba3a9fe8c277f625fae7e1317d034f732995fd88c1461463edd0f2b86d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xabd8b07d34a93e4dc68fe6115af72218e56ad477e848d0d1036114aee03cba10ab561d1570bae237d94ff4d6aa029c760cf2ceda174bbce181ca2e9ede9b7f6a466d2e2d4a38b7367124bc70c887b69770938d3f82c7263b70d0ab960438afa8" - deposit_data_root: "0xdaa5f5480bd46b815e89b67e6ccb84dab0b44ccdc687017785f0b26647eb2da9" - eth1_data: - deposit_root: "0x85ee92997d8c724afd6bfd6078f6b2fa8aacf96a8e98018ea21832f3b4df7d56" - deposit_count: "77" - block_hash: "0xf3f609e1b0e8c5ce0c404929cf2146d97e053f2a7c4dc186701fca3c81c79424" - block_height: 78 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - - "0xee021dc6246d96e341b4df28f5db26a62e2d9716eb92394f5bd7fa7e246fa85e" - - "0xdaa5f5480bd46b815e89b67e6ccb84dab0b44ccdc687017785f0b26647eb2da9" - deposit_root: "0x85ee92997d8c724afd6bfd6078f6b2fa8aacf96a8e98018ea21832f3b4df7d56" - deposit_count: 77 - execution_block_hash: "0xf3f609e1b0e8c5ce0c404929cf2146d97e053f2a7c4dc186701fca3c81c79424" - execution_block_height: 78 -- deposit_data: - pubkey: "0xa7f5d408af436d71ec7acfe9a4592679649d326c00ac92c6f3332423be30c3601d232f265078f1f2a5d6d6cde08de7d7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb3b301cd266d7dd40fed80f8f1609ca35256d263b00b16eed767dfe5ea8116eef1f6cb7f25028c98a9b28357b56ae1890b418858a4500034887ef60b7cf0f8b0f7a8718d450687316f7397632a3c167ecdb2db26694a8608ae06b0dc782e9c7b" - deposit_data_root: "0x19f237b217967fe4d2ddbc826bf6e2c33687cea82a120e7b85e412d7e137d579" - eth1_data: - deposit_root: "0x5ad46efc2ae1bd120b59461c83d8799b741ad5000b754d6f9c20de9115890054" - deposit_count: "78" - block_hash: "0xbf4d50dea5661ab07e15967d8fa2c51cc55395749405ccb6a252ee68f8be1e01" - block_height: 79 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - - "0xee021dc6246d96e341b4df28f5db26a62e2d9716eb92394f5bd7fa7e246fa85e" - - "0x1d7bb861e983c4afb9abbd3c0f7546b0b8e381c2d7679f6993d4ef95f55f8127" - deposit_root: "0x5ad46efc2ae1bd120b59461c83d8799b741ad5000b754d6f9c20de9115890054" - deposit_count: 78 - execution_block_hash: "0xbf4d50dea5661ab07e15967d8fa2c51cc55395749405ccb6a252ee68f8be1e01" - execution_block_height: 79 -- deposit_data: - pubkey: "0xa8be337b3d0e6be415dcb037b246831f9966aacef62b69d6b609e4ff8208bc536c6473bc9fe9e3bec9a8665c8caa05c5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xac120d0e117e12d4af2ed041f4f7bac11ccafbdfdc4047808b83defc328196994afab43ec6581e6e88ce3eade65dab2a11618290151258cfb017fe15ea630f10d3c388c8b8e86a391a82f34c58974509e1b476876bba5b9d5f2c2a6b4682418d" - deposit_data_root: "0xaf7bcef44525d9887a3a9982947bcf876e369190201ca2ea096bedf13edf353f" - eth1_data: - deposit_root: "0x8949d7d74a871960abf40a678878aa93c2d2a3e38bb9fcc9a055d19f434d9cbc" - deposit_count: "79" - block_hash: "0x881de104237f77e44ad277325f56e42735a577d9ea660124ecb08597585991ee" - block_height: 80 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0xa1e9ca708d13a0c759703ba39fd1bcb39b5114273ca6dfff1e76d5c2c687b2c9" - - "0xee021dc6246d96e341b4df28f5db26a62e2d9716eb92394f5bd7fa7e246fa85e" - - "0x1d7bb861e983c4afb9abbd3c0f7546b0b8e381c2d7679f6993d4ef95f55f8127" - - "0xaf7bcef44525d9887a3a9982947bcf876e369190201ca2ea096bedf13edf353f" - deposit_root: "0x8949d7d74a871960abf40a678878aa93c2d2a3e38bb9fcc9a055d19f434d9cbc" - deposit_count: 79 - execution_block_hash: "0x881de104237f77e44ad277325f56e42735a577d9ea660124ecb08597585991ee" - execution_block_height: 80 -- deposit_data: - pubkey: "0x93bb1c86717fa7303f65cb8c45c9fcc8fecb88428b7cd1dd59967a132109c25ab5c97888e46c5d471ff911c573f45a34" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb4ac35f3fb67d9a5ee84dbafbbe9fc2cc173b4198d3369a3ea40b745ff645c269ab3e8742b4033763d59aa88042ed040189f8093e55e38d0e202093c73bf469a35af5a340647247ce3377b213c6b2505bba028436173fd09ee845305a906af95" - deposit_data_root: "0x65440def2a2b71e4e27356ec9dc2a661d5f937f8f0543a8e76c7a8dcb7d8e500" - eth1_data: - deposit_root: "0x2a8a1fb507e33b36be62ba505ce068295dbf1d40538474fb1446b02643b8dd4f" - deposit_count: "80" - block_hash: "0x53a3edf49a1a316d10285385209c510be04cde3a50f816beeb5da44196e426ac" - block_height: 81 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - deposit_root: "0x2a8a1fb507e33b36be62ba505ce068295dbf1d40538474fb1446b02643b8dd4f" - deposit_count: 80 - execution_block_hash: "0x53a3edf49a1a316d10285385209c510be04cde3a50f816beeb5da44196e426ac" - execution_block_height: 81 -- deposit_data: - pubkey: "0x815042c33c1a43c1ee58a58ee074bc93a13c23a035dedee6879730220379d0c03ff4a3829240b6c34e56feb55cd322df" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x951ce0fb4767567fdc4ac1c2b999d39624eb1915b3f74939685a99a30b99212f57eb17b163e3544d1524ada89d3d4fc9195163d5877a53b1d62fa210327eddb6b42150527df562ac5a67a0de3e59e3696a129c3c71c21240b584fe60cae66142" - deposit_data_root: "0x6a9f3213a02e902c041ba8261f093e6c100dceca1ce46fc91420ca0c0a900f20" - eth1_data: - deposit_root: "0xcec66765569321e99f2a977b9ea2864097333eed6bc258e903142b60a8605ea8" - deposit_count: "81" - block_hash: "0x4a84e0118e1e4ec671bc23c123591f08b2ad83363a68941d1fcc2073a50d715f" - block_height: 82 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0x6a9f3213a02e902c041ba8261f093e6c100dceca1ce46fc91420ca0c0a900f20" - deposit_root: "0xcec66765569321e99f2a977b9ea2864097333eed6bc258e903142b60a8605ea8" - deposit_count: 81 - execution_block_hash: "0x4a84e0118e1e4ec671bc23c123591f08b2ad83363a68941d1fcc2073a50d715f" - execution_block_height: 82 -- deposit_data: - pubkey: "0x8be11e9ead2e1bb5be7e2ec066ff83589558a5d9373666b3fc518a6a6639b3baecb87f8f34895f63e8d09d270d93ce04" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8a9863487be81d3b7a8fb0ce2d141843c43751e3f030529f2cd271968671da68c520bf86e8ad56786cf4df9023bd80380bbe0bf6eea87b99a2dc70d3de0d4bb1507fd4fb8f3aca17ac37b71bb11c0cb003cea26a38fd84a2da2e6a9922ef8472" - deposit_data_root: "0x27dc8639ac98670dc00960901ffe78d2ac05ef71bebf1734b839f89f24137300" - eth1_data: - deposit_root: "0xf34475554a62ca5b35a9a0c54a8abab231ae1f7db3098144154435c6d5d51d23" - deposit_count: "82" - block_hash: "0xdc459617e7f73073e5134da264c9ddad3ea4fed09245b90f13ba139a899bbb85" - block_height: 83 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe1af0aab0c08eb08233a563ec4bff65ad501d92c9a40ce2dc785d145c76117b2" - deposit_root: "0xf34475554a62ca5b35a9a0c54a8abab231ae1f7db3098144154435c6d5d51d23" - deposit_count: 82 - execution_block_hash: "0xdc459617e7f73073e5134da264c9ddad3ea4fed09245b90f13ba139a899bbb85" - execution_block_height: 83 -- deposit_data: - pubkey: "0x8bf2630491d2a480ec243b00d65d76e69615e67d3df5d8c14ca7506edd8e896a9083e8ee9e4129af0f6d896a3225c08c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa12d975029e83dba3b911bea41a5f1090fbd2129472a2244171f57ad1fe0f44043d36cd99501aade7f9ed8ce2b36b567025b63e075edf6ebfa4ea3b262ada83fa8872244bb5fcd4f61faf576bfd704bbbf7c1729610a10f1b316a2edcab8f2a1" - deposit_data_root: "0x1657e7e852988d74eadcdd365213af142142f68dfb32ae071af7b9640a7b1724" - eth1_data: - deposit_root: "0xfcaec4c59f62d7cb9fdc1539228db986aaeaf6ce6182e9356d0d7ac9554cc5b7" - deposit_count: "83" - block_hash: "0x1b1fcd8f43efc0746a5c9a5e6069bcad198f4fb06ea4aa27fb2099886f74ea92" - block_height: 84 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe1af0aab0c08eb08233a563ec4bff65ad501d92c9a40ce2dc785d145c76117b2" - - "0x1657e7e852988d74eadcdd365213af142142f68dfb32ae071af7b9640a7b1724" - deposit_root: "0xfcaec4c59f62d7cb9fdc1539228db986aaeaf6ce6182e9356d0d7ac9554cc5b7" - deposit_count: 83 - execution_block_hash: "0x1b1fcd8f43efc0746a5c9a5e6069bcad198f4fb06ea4aa27fb2099886f74ea92" - execution_block_height: 84 -- deposit_data: - pubkey: "0x914b56f41c411fbfca9dc9763f44daf253c103b162457d07954fd0af768b5e74692b4639c22455fb81d71f7ed6144514" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xabbd26d91fb83232422509d6ca45b721185fa61fe610027a06c510dec708eea6cf54d1d955b676f5b77b141a6696f18d18b7336ede4da02504ca96e3363ce3c91698ffcf904c9f3c9d31648c79263acacb9747d7069d79016791b4facce83555" - deposit_data_root: "0x4123a31554edb843e51c2c6d3d608693fda4d5def338ade6425f1790fb511774" - eth1_data: - deposit_root: "0x15754d151c1cae6449d10e2e95489aeab66d5defa0f19c29635f0319ff830a28" - deposit_count: "84" - block_hash: "0xa18fcf7a630b971e354e7c3661f21b45306ba7ff6d02a67a7a9fdce708a97805" - block_height: 85 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0x78b501b3fa5475f83b94eafea06a4d859d98ca9dd9efb1f7c9f440868791c620" - deposit_root: "0x15754d151c1cae6449d10e2e95489aeab66d5defa0f19c29635f0319ff830a28" - deposit_count: 84 - execution_block_hash: "0xa18fcf7a630b971e354e7c3661f21b45306ba7ff6d02a67a7a9fdce708a97805" - execution_block_height: 85 -- deposit_data: - pubkey: "0x8794388915e86e4988363cdd4289ad19182209c873cbbbf5a80ff5c99f93acb839807787a77ad2b603f074405d7ed08b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x87e0e8c98f9e186d31d992634611b6d08376911636fd1d689b01330e70b4a9c819ddaef93128bb333bfdaf9e6e2697550897b9be994b5033b6397989e1db1b7db9d889083f4ad1c763192da282943f571d77781ef46364757151aeae27740ac8" - deposit_data_root: "0x55aa7b5a4c6d119979913fa560116bd32289f6636e12f6056017cdef97b55682" - eth1_data: - deposit_root: "0xfa36dc4e7091338e1af1a655d7f73c8a4a31453769962a8335196409cdd05552" - deposit_count: "85" - block_hash: "0x6706a23dd2a500b2856ef97cdee6757a46ab118f06df889d50afc259c7938558" - block_height: 86 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0x78b501b3fa5475f83b94eafea06a4d859d98ca9dd9efb1f7c9f440868791c620" - - "0x55aa7b5a4c6d119979913fa560116bd32289f6636e12f6056017cdef97b55682" - deposit_root: "0xfa36dc4e7091338e1af1a655d7f73c8a4a31453769962a8335196409cdd05552" - deposit_count: 85 - execution_block_hash: "0x6706a23dd2a500b2856ef97cdee6757a46ab118f06df889d50afc259c7938558" - execution_block_height: 86 -- deposit_data: - pubkey: "0xa3862121db5914d7272b0b705e6e3c5336b79e316735661873566245207329c30f9a33d4fb5f5857fc6fd0a368186972" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x86fc69a33aaa4221dd112b9c1d69633430d803b55fd0e046d17e9ecc02a53d05429034baeb7d235bc76573449bdd7d0d05c80a3cb7e785f1a06b40cea3fbf66ceefc0a542983a92a609051580f48980a42f3cd43703b810f0f5ee8b5c2a5e556" - deposit_data_root: "0xead7c5895bcb78cb6bf3b7dd8c3e984cd33de92cedafcbfb11821d725a1a7008" - eth1_data: - deposit_root: "0x55993260810877f669acb1d186a3b2e6715d9ff9d948cdc4da03a0515dd3fb03" - deposit_count: "86" - block_hash: "0xa2ec2792a7481050df8e4c66e3049fa01b122a59b4b0da16eefb2160122bd887" - block_height: 87 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0x78b501b3fa5475f83b94eafea06a4d859d98ca9dd9efb1f7c9f440868791c620" - - "0x05fdfe681bbf2653770a5add8d5a186497090b0852abfc6d2b5c7cd9c2cd48b4" - deposit_root: "0x55993260810877f669acb1d186a3b2e6715d9ff9d948cdc4da03a0515dd3fb03" - deposit_count: 86 - execution_block_hash: "0xa2ec2792a7481050df8e4c66e3049fa01b122a59b4b0da16eefb2160122bd887" - execution_block_height: 87 -- deposit_data: - pubkey: "0x96ef954b331a534199f4f113d993a50ec7a781fc5aa2a181ea0bdbfd4c5c557abfebfcc02604d5aef52ba64afbe0ff18" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb64dc6b6527edca3c16cf0dd04aeda244d69cb84a96e951ed1a7286c0a8ee24b56d4651e14e03d472ae3ed0cc9928dc40c5947e185e67716db8e8bd4c85bb5ee4bd14102347117b90276620bda17f0a68b6ff225842814a7e0f15e920350725a" - deposit_data_root: "0xf962e2d35918b11c748e47d10d66e0e052bfb45965a88ca185044e820b33f0e4" - eth1_data: - deposit_root: "0x1e14f4e10d68971bf523b164e176f135b1304cfa07c88a57233d8b39a6c83456" - deposit_count: "87" - block_hash: "0x3a5d185a584fc74290efc7f9f7781c00d96708e925061731d5e083d44ee009f7" - block_height: 88 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0x78b501b3fa5475f83b94eafea06a4d859d98ca9dd9efb1f7c9f440868791c620" - - "0x05fdfe681bbf2653770a5add8d5a186497090b0852abfc6d2b5c7cd9c2cd48b4" - - "0xf962e2d35918b11c748e47d10d66e0e052bfb45965a88ca185044e820b33f0e4" - deposit_root: "0x1e14f4e10d68971bf523b164e176f135b1304cfa07c88a57233d8b39a6c83456" - deposit_count: 87 - execution_block_hash: "0x3a5d185a584fc74290efc7f9f7781c00d96708e925061731d5e083d44ee009f7" - execution_block_height: 88 -- deposit_data: - pubkey: "0x96c8d3dd08724624017f178393d176b425dab9dfa1cc3f62c7669337446baa601e0aa261c00c76bde07ba9a1a3582c0a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb37b23e995a74a1c72a6e5d0dc75aa44d3e560c5c3aeaac78648ccdd38380e68b321cfc39555af65cdc2dbaadf3eace20011622070f90f2e782e5edff8330c8b3d8623fbf58fb878e0645f3588b3f75b293fc5f8051d7c324ccb9df12b48a07c" - deposit_data_root: "0x0b8d152337bd30a81e3353cd2ef3767d4a3f6b84c124010ec8eb784591158e17" - eth1_data: - deposit_root: "0x4800ae85e0ddc8eb4927b7c3ac52f3718e7a47eee6b9490d4d974deff6b09f16" - deposit_count: "88" - block_hash: "0x6bf5193552720db315898ec262ab1cfd7590e13a88ae86efc702243acfc001b8" - block_height: 89 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - deposit_root: "0x4800ae85e0ddc8eb4927b7c3ac52f3718e7a47eee6b9490d4d974deff6b09f16" - deposit_count: 88 - execution_block_hash: "0x6bf5193552720db315898ec262ab1cfd7590e13a88ae86efc702243acfc001b8" - execution_block_height: 89 -- deposit_data: - pubkey: "0x92bd81b8e9099b9ca87a2033fdd84475752dc34a0fae0a8e50aabf4d3baff9cd45ed56508c837023944350f53dbc4ac7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x88835cc0416f137bf06133717762b9bda8e31f66b6910eb55a8eae0a4d97bb8d51284a8c4038da5e2b15b77e3beaee6b09d1561bb5bf1ff2cd2f7e6396cb80c520f161f43fb01dc259f5e2c2f33dc632ff1a6a14b46cade75b0850d3841e666c" - deposit_data_root: "0xd3fbf626f2db8d53f0cfa2e8ab199e5633cbc1b1bc01dd5579000ae8b4c59525" - eth1_data: - deposit_root: "0xe83ff8243a690675111ffaa012be7ff8931efd74d0d29c99f7c543db94fb2e68" - deposit_count: "89" - block_hash: "0x1b93e72ce9ac8a2eaf1c6b6e8ceb0d413e084b62ab3b65a33414f794c6bca144" - block_height: 90 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - - "0xd3fbf626f2db8d53f0cfa2e8ab199e5633cbc1b1bc01dd5579000ae8b4c59525" - deposit_root: "0xe83ff8243a690675111ffaa012be7ff8931efd74d0d29c99f7c543db94fb2e68" - deposit_count: 89 - execution_block_hash: "0x1b93e72ce9ac8a2eaf1c6b6e8ceb0d413e084b62ab3b65a33414f794c6bca144" - execution_block_height: 90 -- deposit_data: - pubkey: "0x83802cd575a3cea7e3e38fc1a73d94a9e4fdb999b8494e7929309c009d79a23edb1ba091ac02588f130e0585fb106540" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x90b696dc285957fee877e14c40dcd159622b4522ddb9e31d10f5d89d83af25c7a65de87deb816ec3fea86251c38247bd03e0e4e083e8ce7b63ed5917fd5b7a8bcea01ed61b3234b62eb0baeaf253cc04655cd5b64e7d642a3bc7be5e4a32634a" - deposit_data_root: "0x58ea01dac688f2b29e401e7edb6319c7017d12c8adc57b372865b40d907be8d6" - eth1_data: - deposit_root: "0xd8b0fc873ba2d2d52ef3e7985d6ef0cc924ce56c852970823814b95892805127" - deposit_count: "90" - block_hash: "0x666278a6a24e797f1040470692822b240b2630b0af2a38c26da23f727af7780d" - block_height: 91 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - - "0xe2fdce2d1fdc2fe192bb4aa9293cebebd50dc5e78e38a1bbc1b9b4fd39e22985" - deposit_root: "0xd8b0fc873ba2d2d52ef3e7985d6ef0cc924ce56c852970823814b95892805127" - deposit_count: 90 - execution_block_hash: "0x666278a6a24e797f1040470692822b240b2630b0af2a38c26da23f727af7780d" - execution_block_height: 91 -- deposit_data: - pubkey: "0xb451eb0ff4990917aba6e3d80c34aee91ea1ce49053f38ae174cef107cb9acc595d0ca3fefcb804c9dd04510c630cabe" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa5e3f58c8249a0d3c8269f16af4a8fb42b20b821c1554e51443c897ff00389cb8b85cd953236bc22430ddb49e3b1d8b018926d1e5b87a614e1b9257838cecf59eac47ecfb8648c930d839b8524ade0b68036e9e9987c554cfa24417a00ffc40" - deposit_data_root: "0xf11f85f05b6d4b0d8ff3bfdc09e03ece905ba6452e2e45f20c78e62652a71f6e" - eth1_data: - deposit_root: "0xc1dc1c55036f14ded17a34c5d6c82a3df5c704a067ab5d0f9d50481679c9345b" - deposit_count: "91" - block_hash: "0xcf447d1821972d4a820ab84a95dafa442735e577290d6f110ee498567a0a1662" - block_height: 92 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - - "0xe2fdce2d1fdc2fe192bb4aa9293cebebd50dc5e78e38a1bbc1b9b4fd39e22985" - - "0xf11f85f05b6d4b0d8ff3bfdc09e03ece905ba6452e2e45f20c78e62652a71f6e" - deposit_root: "0xc1dc1c55036f14ded17a34c5d6c82a3df5c704a067ab5d0f9d50481679c9345b" - deposit_count: 91 - execution_block_hash: "0xcf447d1821972d4a820ab84a95dafa442735e577290d6f110ee498567a0a1662" - execution_block_height: 92 -- deposit_data: - pubkey: "0xa7f711233af57440e9ea700113fc4dbaef97e7da7741dd2e38ae668a7f2685d4585d54a9e6712ff1b87c69dbb181abf7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa2e034a05c1d3cac573a3cd1a4d76584fe1c12dc828aa85fe6b14958efb476d2e5afe6df9f7f7b8af02f19c0f74d93fc0e94911f2152659c11246ad51e5174d1fbcd9335c60fb3097c8168793238d0e0ff710b70f7bfe7f321131271bdff8dc7" - deposit_data_root: "0x548e5a3a2012b86724762a43fc45047e7e8092914d1b2188a583ca9903f96ca0" - eth1_data: - deposit_root: "0x6d4e79bc326336e3a6c8e6c421a29d877c8daa3956aa1e55a63257ab6f5f43af" - deposit_count: "92" - block_hash: "0x631ee71ad7fec6f24ca2a56cd1c45f6c860b4ca487b28365f65860caed0614fd" - block_height: 93 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - - "0xf5e222e2ec0403f08a8f04b439d8af9798f665fcc237f672d6e5e7484a024866" - deposit_root: "0x6d4e79bc326336e3a6c8e6c421a29d877c8daa3956aa1e55a63257ab6f5f43af" - deposit_count: 92 - execution_block_hash: "0x631ee71ad7fec6f24ca2a56cd1c45f6c860b4ca487b28365f65860caed0614fd" - execution_block_height: 93 -- deposit_data: - pubkey: "0xaca5e4979f281b5ab0ea0f549d6dcc34989607c335e94efedeffc7e73b393f42c7b11d76144a750f82600b21d10b6777" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x90374711b91d6b7385cf3f099f5ed3a93d79383968645d9bdbb191bc461b3559df4a7bef411c33a692590ffa1aaaa91f1870a5c0d20df79f2cfaf2d5482f49f380679168f47affe35a4fac018692f5f0f007031bb3d8fef31e23a922a444d8b5" - deposit_data_root: "0xfd4f2e7d7c027a545467d3a3295b2ab5ccfebc669330a533243db77cd3e8cf4c" - eth1_data: - deposit_root: "0xdda9d42bf636ae45f5c4376a0cb0b56e15846bc9119dffda44e196de5ba747e4" - deposit_count: "93" - block_hash: "0xcb69ea6fddd200d561ac5e304e8b08abc9b6ec65c3f0e4cccdda7f523facdd6f" - block_height: 94 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - - "0xf5e222e2ec0403f08a8f04b439d8af9798f665fcc237f672d6e5e7484a024866" - - "0xfd4f2e7d7c027a545467d3a3295b2ab5ccfebc669330a533243db77cd3e8cf4c" - deposit_root: "0xdda9d42bf636ae45f5c4376a0cb0b56e15846bc9119dffda44e196de5ba747e4" - deposit_count: 93 - execution_block_hash: "0xcb69ea6fddd200d561ac5e304e8b08abc9b6ec65c3f0e4cccdda7f523facdd6f" - execution_block_height: 94 -- deposit_data: - pubkey: "0x984620db3658a19769475080998db9e7f5bcd4255a89a70b5ecf7db01226f213836d091a3b37eb96e4937966b094a291" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96f074fc891b637e18eb0b3c5908f94d4e71f76243914e3b3a88348b7a7ec85289d322e24f14ce75a7d193dece4c7cb5150aca3dae194839807c09039304a894c9e5b70735b88c437f9c6043e495201f58a28683d2eb528e3e81fe14b3ce867c" - deposit_data_root: "0xeaad4c2435c42339d3f2248def6f6d44a0da2237d6eb6092016e5b9a47b3fa9f" - eth1_data: - deposit_root: "0x026d58bd6b7169617f62fc8725cebcafd8b3f352d63060d3b3326ff6e9d2d3da" - deposit_count: "94" - block_hash: "0x562a78b2c5ec97a0b4fa413f57885311a457155372bbf630a434d8c243d21036" - block_height: 95 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - - "0xf5e222e2ec0403f08a8f04b439d8af9798f665fcc237f672d6e5e7484a024866" - - "0x621b699cf7502853da163711315ae56eac3eca5afb2c480325bb9a35e3c9f37f" - deposit_root: "0x026d58bd6b7169617f62fc8725cebcafd8b3f352d63060d3b3326ff6e9d2d3da" - deposit_count: 94 - execution_block_hash: "0x562a78b2c5ec97a0b4fa413f57885311a457155372bbf630a434d8c243d21036" - execution_block_height: 95 -- deposit_data: - pubkey: "0x8f1ef3639aea57fef705847e251b785bb608a848f42d9107c494cbc696be35642f6552fb83174ca2e73632568a5667f4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa733bafcafc2ba319538ce6af3de20976c5ebd8040a8a3628f7e69a16baeb95566b0cefc55472fba696aa023001b52f605d8824f7dbf2dba9ff2a53554c155815da39e3f27ebd7022a52412b9a793468cb1a1c6469aae7750a1d36a0010fc8b8" - deposit_data_root: "0x93614f1ff88e971226eb167a407e0afe4d236a82e2eaf43259946edbc8af576d" - eth1_data: - deposit_root: "0x511e1b6a894a5783751f3eac7f79d3f52c94a76f29686ebe7eaac7a854d490d1" - deposit_count: "95" - block_hash: "0x723848008dec04dc63b5a521c38e95c5f2598053fb5a756022d3a85906128eee" - block_height: 96 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x9cdee5c9ecd1ef9a54feb904550ef7271277dcbc43318688b0b0f4b0454c5a95" - - "0xe78ee87ce4f2c19b2b27b0e4d875fb21bf9580e9b8e683f34db124274f263dc5" - - "0xf5e222e2ec0403f08a8f04b439d8af9798f665fcc237f672d6e5e7484a024866" - - "0x621b699cf7502853da163711315ae56eac3eca5afb2c480325bb9a35e3c9f37f" - - "0x93614f1ff88e971226eb167a407e0afe4d236a82e2eaf43259946edbc8af576d" - deposit_root: "0x511e1b6a894a5783751f3eac7f79d3f52c94a76f29686ebe7eaac7a854d490d1" - deposit_count: 95 - execution_block_hash: "0x723848008dec04dc63b5a521c38e95c5f2598053fb5a756022d3a85906128eee" - execution_block_height: 96 -- deposit_data: - pubkey: "0x8967da3c8071ba2bf632cd40ae08fbbf0a203c47c02af1948fc232a7a743c0c0cfbe51606b89f102f2f6de7f039fb155" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x913fdc3ce1efa57fc787572b38a1682a411548e5707daf8e7b9e6a6126ab172c33226e21dff8bbd48b2a8d22f1a8b0e51540166b4c6f8cdf978f69a04a3ec48ebbf64d9640e20b834b56362b0bab8ad1793f72bce5d6afe74588a6f6efdd7267" - deposit_data_root: "0x6b4666b947e91abcf56197ff3e4c918aee6289bb0dade7a5788d7032ac026acc" - eth1_data: - deposit_root: "0x083b92e2d2cee3120c5fdc9d2ab492d7563bec65eb27aa5013465136f7eebe41" - deposit_count: "96" - block_hash: "0xc566f29d941053e9c2881d01fc9b1314c714fdc6d6ef63d842e557e6a347884b" - block_height: 97 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - deposit_root: "0x083b92e2d2cee3120c5fdc9d2ab492d7563bec65eb27aa5013465136f7eebe41" - deposit_count: 96 - execution_block_hash: "0xc566f29d941053e9c2881d01fc9b1314c714fdc6d6ef63d842e557e6a347884b" - execution_block_height: 97 -- deposit_data: - pubkey: "0x8d58f7e2e58471b46d20a66a61f4cde3c78ab6c0505517c615e08d8ef5adf59b65fa2b01ea2395c84584a6f10d6cee2f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x892b60039b6e05f3f82b460d9360e1f1121adc5974a7c75f8cb723d597f764fb26aaf21bbd6a08b36078d6acc00a355607888b4838f9a1d007a4d0d0db5650e36ca498d6d074bf14a0a33ef13c34a419d7f99c6ebe0eca5001263cbb26f299b4" - deposit_data_root: "0x34e3e3e0c13a79cd9e7e8826d9d20c106a9401f253750a68869eb8d31fd8d03a" - eth1_data: - deposit_root: "0xc33b108e1f20262d0bd6dde0604c39e305ecf07eb8c390070cf586395b6050ff" - deposit_count: "97" - block_hash: "0x01a6a54f02fad6d7168ef158896c42874a9696b7d6b33b6a90fba4e9892723a2" - block_height: 98 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x34e3e3e0c13a79cd9e7e8826d9d20c106a9401f253750a68869eb8d31fd8d03a" - deposit_root: "0xc33b108e1f20262d0bd6dde0604c39e305ecf07eb8c390070cf586395b6050ff" - deposit_count: 97 - execution_block_hash: "0x01a6a54f02fad6d7168ef158896c42874a9696b7d6b33b6a90fba4e9892723a2" - execution_block_height: 98 -- deposit_data: - pubkey: "0x8db9f236d3483af79703244c7034b5267a0546c3c840d4e91fdcdd466373d62d960553982225ca5f7666dd7375a29c19" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9395087be80e3bc55d623f4d5bdfe6de200064e27d862d731c0c3dcf60a7c73fc6ad5c59177f788b5ce4ad6c2053fcea0950ec9196c8deae651f8cf3042a87b234bb4f201fe0d852708c332d0efe4323a7f6b3782ee919b365479cb5aa90bcc3" - deposit_data_root: "0x57a75df5bd0f09fbaa3bbd2ad872fd0704d94d51ddcefad7ad9af438b9d63ce2" - eth1_data: - deposit_root: "0x99b08e97b9635d5ce40146efae53e688f53a8910a0357fe211440987b63f86c9" - deposit_count: "98" - block_hash: "0xff6e581a1f58dd834e095b5dc6cd14940a0b5148a7d0b6d94944ee324750abeb" - block_height: 99 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x9c30281da5fd2d7a8bd8e4c8c3a19ee28bb8a08c97e386825abf49429dfc8492" - deposit_root: "0x99b08e97b9635d5ce40146efae53e688f53a8910a0357fe211440987b63f86c9" - deposit_count: 98 - execution_block_hash: "0xff6e581a1f58dd834e095b5dc6cd14940a0b5148a7d0b6d94944ee324750abeb" - execution_block_height: 99 -- deposit_data: - pubkey: "0xb7721412ae5a793f34ac8866698b221c67ef8272eba44d3030512ec3f7ed8ffcb620b58f17809690d5276423e849827f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8967a6b4490020cee91b6554b3aadb730da19f1dc9ab10629014f1b6221bb3f5827dafab1c48d97a5e0f55577708dad613724b60700424854c38a14aae340d8fd20ed7145e95dc458cc3d298192aad68c112bccd7f5574594a6d5a5b5401bd63" - deposit_data_root: "0xc14a33eb96c6282e934f2944a23bafc7813ab96c21dee6ec6b2ad8c4c10ec4e4" - eth1_data: - deposit_root: "0xcaa6c2f895d078431ed03bc0d16ff9cae6eab44c2675a42de52820216f647f0e" - deposit_count: "99" - block_hash: "0xaf681b414bf20e9775a4c897373013a58d6db4e23c2a394b25cd9dab40ca6c53" - block_height: 100 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x9c30281da5fd2d7a8bd8e4c8c3a19ee28bb8a08c97e386825abf49429dfc8492" - - "0xc14a33eb96c6282e934f2944a23bafc7813ab96c21dee6ec6b2ad8c4c10ec4e4" - deposit_root: "0xcaa6c2f895d078431ed03bc0d16ff9cae6eab44c2675a42de52820216f647f0e" - deposit_count: 99 - execution_block_hash: "0xaf681b414bf20e9775a4c897373013a58d6db4e23c2a394b25cd9dab40ca6c53" - execution_block_height: 100 -- deposit_data: - pubkey: "0x99f6e5b80dc52407f0436d3474bd5da5ff23a19cb188b933af6312d9793cbfd54f9e72596c5d481a1ed8d705b81c1f0e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa0de9fe9ab9d8fa0b287ca0c7335bca94a79a888e67a7e0a933d38b5dfca2cb30d77f75398652a3c787af48b0707ebb20d3fd2e3f78c459235bd0eb17f550964e6d2638470728499f59cca4c041846d433e9095b5d2cc83c633cc728f3683db8" - deposit_data_root: "0xc6f502475ac7546b770905653f9c9da144d8721ebf626232259099df44d67793" - eth1_data: - deposit_root: "0xe346f5cfac0d8834bbdf80742e9de3418f612bc03fa79a9be61a9a201070673e" - deposit_count: "100" - block_hash: "0x40078f8026a5491a9530ebc2bdc366535d4c6a7c37c1df6515c4074508849187" - block_height: 101 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x2c03f5ec194e16617790b85ceb15755990f694b3fb90b74d26812f265b90d33d" - deposit_root: "0xe346f5cfac0d8834bbdf80742e9de3418f612bc03fa79a9be61a9a201070673e" - deposit_count: 100 - execution_block_hash: "0x40078f8026a5491a9530ebc2bdc366535d4c6a7c37c1df6515c4074508849187" - execution_block_height: 101 -- deposit_data: - pubkey: "0x8931cd39ec3133b6ec91f26eec4de555cd7966086b1993dfe69c2b16e80adc62ce82d353b3356d8cc249e4e2d4254122" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa1e9f2cddc66b67063b4caecedc4093ecb9241a751da05b6219b5062a5311590d0b4f8b258377a6696316d7808e10c6502542f3a14c00693a70ea8adf2bb74b79d0a15613058c521b50882007b85a0ca26b0bf888695d0a03dcdf0139364c849" - deposit_data_root: "0x61a0ce23d0255c07b5f1f85771735ffc18dd898b056d00d5e72431c9ab4526c8" - eth1_data: - deposit_root: "0xd5db7d15947a7d071f2809c8e827e9ca01511010e1af5e50e59ed2eae150fab5" - deposit_count: "101" - block_hash: "0xbfa88dac097016410b459589b3d5703070fb2da3419661138f0e3e9bc868e49b" - block_height: 102 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x2c03f5ec194e16617790b85ceb15755990f694b3fb90b74d26812f265b90d33d" - - "0x61a0ce23d0255c07b5f1f85771735ffc18dd898b056d00d5e72431c9ab4526c8" - deposit_root: "0xd5db7d15947a7d071f2809c8e827e9ca01511010e1af5e50e59ed2eae150fab5" - deposit_count: 101 - execution_block_hash: "0xbfa88dac097016410b459589b3d5703070fb2da3419661138f0e3e9bc868e49b" - execution_block_height: 102 -- deposit_data: - pubkey: "0xad01d0f23cb74fcc4c39a2d0827d22f4722f02076196350dff5dcc6be765009c66e29001001959d77b277c2f0fba0425" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb3ffc0107b268976d973c779f1682eb3b92fa5988a72a169d6b959472a72b9ea48c30132bbfac1dbde2a1b658007025011969607d97747e6bca0a852a0c0a03d0fdee3550f22f4052fae270a373b92d40f14c86ff0f79d7570335e16269b034a" - deposit_data_root: "0x7fbb1660d3859504d5652fb48a640e3a0852b9087209abc9e9a641a3b0bc526c" - eth1_data: - deposit_root: "0x2a0a58d87c8644cff8c8b70ca8c8e698376bed37ba7f9b4ed5f5e9058b3e4017" - deposit_count: "102" - block_hash: "0x2617746c889290b55facaa523ef25b1bf1bfad0be77797ffa78bf2fd44217fad" - block_height: 103 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x2c03f5ec194e16617790b85ceb15755990f694b3fb90b74d26812f265b90d33d" - - "0xde2e3c85af638e1c1b8581fd99aafbb0b9e0e2d638a96c2037814fba4940ddad" - deposit_root: "0x2a0a58d87c8644cff8c8b70ca8c8e698376bed37ba7f9b4ed5f5e9058b3e4017" - deposit_count: 102 - execution_block_hash: "0x2617746c889290b55facaa523ef25b1bf1bfad0be77797ffa78bf2fd44217fad" - execution_block_height: 103 -- deposit_data: - pubkey: "0xb300303a03b8eff26a25449169d1946b208d5240f011ca6f5db23cd7f2c004b63f60afe3c9e047b67f9e4c8970c71cf0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x88d4150448307f43753ee083124754d8c18b2c4eadb170e741e828b61b356b8cf3c69caadbdcae96f3353ba068f2c2da00596d5ba1f5dae9ec4c305ee7c5069ba5c71ea6ac5cde50bdee887233c18a85928a066802d5b4b38f3295c706860d93" - deposit_data_root: "0xaccb652fae17e3c73511c9d3065377183aa4cc46b7f0f57b0e7d821b2b2ce99d" - eth1_data: - deposit_root: "0x7154ee203a0ef6d25aba1c8473290a20ff6db1147cf850f848d8641e25d872ac" - deposit_count: "103" - block_hash: "0xb082be3f6319b71f9235dd3320af6ac53a7211bd652c673eecd70fa2443d9b76" - block_height: 104 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x2c03f5ec194e16617790b85ceb15755990f694b3fb90b74d26812f265b90d33d" - - "0xde2e3c85af638e1c1b8581fd99aafbb0b9e0e2d638a96c2037814fba4940ddad" - - "0xaccb652fae17e3c73511c9d3065377183aa4cc46b7f0f57b0e7d821b2b2ce99d" - deposit_root: "0x7154ee203a0ef6d25aba1c8473290a20ff6db1147cf850f848d8641e25d872ac" - deposit_count: 103 - execution_block_hash: "0xb082be3f6319b71f9235dd3320af6ac53a7211bd652c673eecd70fa2443d9b76" - execution_block_height: 104 -- deposit_data: - pubkey: "0xaca096c7f41cfa6b9317dff26c6c96878c9e5d5eed50afde44d8df206372ad4b4c45568f6671552029f4c3509e295bef" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa200bef0c9ca2a3efafb7d75dd19be1f621a50b1f5d3eaa688a6d02119a1c08125c4d9dd71cc388e2dfac5544d466bf618293c90e396b56e0d7ab9714a386e0f9685cd4aa07de369cee98ef3912281f438c7ae29146a199c94d2542889d9f898" - deposit_data_root: "0x70ee28dadd98103e531ac6f411f1798bec743246fe66be847efe6fc26bf530bf" - eth1_data: - deposit_root: "0x548bfd5b5eced68eb9fdbabb484a6145d518a234cd7626ab7bcde2752dc878eb" - deposit_count: "104" - block_hash: "0x1cf1756c5ffc73b0f80fc2a242fedcdeed357eff548826f5b81a765f69f98a9b" - block_height: 105 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - deposit_root: "0x548bfd5b5eced68eb9fdbabb484a6145d518a234cd7626ab7bcde2752dc878eb" - deposit_count: 104 - execution_block_hash: "0x1cf1756c5ffc73b0f80fc2a242fedcdeed357eff548826f5b81a765f69f98a9b" - execution_block_height: 105 -- deposit_data: - pubkey: "0x87bbd5574c17dbf80463d11f812a77306f67913c510b1b234f5bd80478c7da8e69476cd6711cd1f4c0e228a4e2e99636" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x982d4f21d196dd1c646180e68e831f730e596980de02297deb7a91a2d1b5fdf82fe7bd7dd9e2d301ef25481814e763b505f8183b996e08e363c27fc6e6d08538ee37898ac5ea10f9ab9b6f70265ad3b214613e643c9c047333bbaf95053eb3f6" - deposit_data_root: "0x16c89707a34efa444cf234654f40c446d1dc61cd8f9868668df3b1ab87eaea8e" - eth1_data: - deposit_root: "0xef70ec141be46e60cb179e2b7dfa8f061a0ed3cdf8b1f8b67e0f410f3a6af678" - deposit_count: "105" - block_hash: "0xda9c4714b0ab8c1befb967e12d9f677e7c536aca4750647e6302a6c5687460f2" - block_height: 106 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - - "0x16c89707a34efa444cf234654f40c446d1dc61cd8f9868668df3b1ab87eaea8e" - deposit_root: "0xef70ec141be46e60cb179e2b7dfa8f061a0ed3cdf8b1f8b67e0f410f3a6af678" - deposit_count: 105 - execution_block_hash: "0xda9c4714b0ab8c1befb967e12d9f677e7c536aca4750647e6302a6c5687460f2" - execution_block_height: 106 -- deposit_data: - pubkey: "0x89a80c9263a21ebb9b7b99e59e53edc9ac766a55da86a52d1098d57572999ebad7cb92800b1f15be8d7c43889ab71c5d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb6ca12b56b3beb9f9c785f8f865ae5d33ae5d432c34b580907c56ea252be219553c87e3a6d2eb3d93bf29b16c0c522b303c289933ace190bcc666cf3cbcc22f5f516d2ecad7bf29ceef55f8911b33a9c9f7bdde58876e6c02522925d0de0e444" - deposit_data_root: "0xe9b136c1ca2fcb6d303f551b646a882b36d377989a217239b68534d1afc9fca0" - eth1_data: - deposit_root: "0xaeae251b14d94efe7aa55639a1387528a1e5ef2769b05e8286a661d147e06cb3" - deposit_count: "106" - block_hash: "0x5669324d92acbdd26d268054be610287bae0b19c33fc941fd3096db48833448d" - block_height: 107 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - - "0xa2000cb687523633d740002d63b1deb735701652a6d1053a8e859b2a2b17ca0b" - deposit_root: "0xaeae251b14d94efe7aa55639a1387528a1e5ef2769b05e8286a661d147e06cb3" - deposit_count: 106 - execution_block_hash: "0x5669324d92acbdd26d268054be610287bae0b19c33fc941fd3096db48833448d" - execution_block_height: 107 -- deposit_data: - pubkey: "0x802408c2a1901d316637a3ec6d20447bb9ee105c8c088510bfbcf8cda3ffa9376779f36e12e960e7efa5f2aba45e6483" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x91efe4ad425f9226a32e6c2bb04e47bd7adb4ebbbfcb20bf4119e046f22aa714264dcfaa8a1744d02d959e6719fbb245013230f209524365c2b2f418abb365a439883f89abce8896f4463dee7e9b08629b06bf1a1e8704cee65bad429c0a5757" - deposit_data_root: "0x43e5a791b6ea536132e9b7c2e9e154eb7228704fb1749d76474dccf2cffb2c2e" - eth1_data: - deposit_root: "0x97284fc411333bd602aef2ebd5cf41ced3b2d7b27ae5de0d97a0b23f1a352ee6" - deposit_count: "107" - block_hash: "0xb1f82e537255e3d8c6607a9e5acf26cab9c71cc10145c2295012c650f67a3476" - block_height: 108 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - - "0xa2000cb687523633d740002d63b1deb735701652a6d1053a8e859b2a2b17ca0b" - - "0x43e5a791b6ea536132e9b7c2e9e154eb7228704fb1749d76474dccf2cffb2c2e" - deposit_root: "0x97284fc411333bd602aef2ebd5cf41ced3b2d7b27ae5de0d97a0b23f1a352ee6" - deposit_count: 107 - execution_block_hash: "0xb1f82e537255e3d8c6607a9e5acf26cab9c71cc10145c2295012c650f67a3476" - execution_block_height: 108 -- deposit_data: - pubkey: "0xaccc213c82702adfd5c32b24a68863f16ab6ab46947d1d7b3829bc62cd5f2a87bcd0d3ef27d442f07ad4363be9fc12f8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa2e689452fd1534510fd42ff1e2dab4addf50492d66518836649ddf9ca206953fbd0ada1f727d14fb05e2041e3d786e308f06f5cb5f610fb8c8bea08730f4f35cf4ce8e12dbfe2d86f5be234b765373fccf1d2e167d2e14051aabf6c563822d5" - deposit_data_root: "0x834c7ef796fc324ee3d09a5c35fdc887d317b6e6fb166403b37f9672736f83c8" - eth1_data: - deposit_root: "0x0d890ddd7f83f43cdc1bd1888b4b514892240f854310148d31fd0cd36e577d0e" - deposit_count: "108" - block_hash: "0x3859945b847ee6c8bda15345167cd845984f63fa80b59abd02e47f305ab1859b" - block_height: 109 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - - "0x3f0c046faed71f3fab856fcaf43a55a7865e4f8054998ea1b7b0ac4b2ec62d03" - deposit_root: "0x0d890ddd7f83f43cdc1bd1888b4b514892240f854310148d31fd0cd36e577d0e" - deposit_count: 108 - execution_block_hash: "0x3859945b847ee6c8bda15345167cd845984f63fa80b59abd02e47f305ab1859b" - execution_block_height: 109 -- deposit_data: - pubkey: "0xb0af0bfa83f0922e6cbfd2bc8ec19ff0f692fcb87c4e35f30e1353b342ae2fdaea6056bc2759970fc2a1f561826f564e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xac48afbc00ea959cf42b12f84d934ebaa6411c2ef6543b9551795d44b9c0ad242892330217978991c010250104e9b3d702107985f4f3f470ea555d9995fdf2c53549f3f4577e1893cccce684b9f13961730dccab982e8ffc56013ddec229e145" - deposit_data_root: "0xa366a29df760de23293716136774c56eb8d31fc5c2ab2ad91235305fffff9ab2" - eth1_data: - deposit_root: "0x4c52ffd886c8668450508977adb286c525f48706688f364dcd0a7197b08b6dc0" - deposit_count: "109" - block_hash: "0xd5bebc34ca9da5a4ee194fec1c3efdf56a070520be6dbfd87f08862e7909ad5e" - block_height: 110 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - - "0x3f0c046faed71f3fab856fcaf43a55a7865e4f8054998ea1b7b0ac4b2ec62d03" - - "0xa366a29df760de23293716136774c56eb8d31fc5c2ab2ad91235305fffff9ab2" - deposit_root: "0x4c52ffd886c8668450508977adb286c525f48706688f364dcd0a7197b08b6dc0" - deposit_count: 109 - execution_block_hash: "0xd5bebc34ca9da5a4ee194fec1c3efdf56a070520be6dbfd87f08862e7909ad5e" - execution_block_height: 110 -- deposit_data: - pubkey: "0xa626de0451397075bf145e720691c9d5ed92eddf1f4e48155b455aac7a8e920d042f5635c7a74fe3a9175ffbfb7ce12e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb34e59e6935eafbe65e52e28c5f311f381b303f479ec2a6e9fba488e514de0eb6e1079f033cc8dcdd839675a6ef00869132aaa13dae6179fa88d01620af6cdf890258219b7bd4e374f82712cab8aec4de54ea403f2807c20ff8587ba9740de6c" - deposit_data_root: "0x225e8673c5a0ede3e15885c408c04c2468c223f1fff0a87b08cc74887dd3a56f" - eth1_data: - deposit_root: "0x7c63909e1401449ed1e5773863d7629fbc8396468b85476a75d7e637b5f9f7a0" - deposit_count: "110" - block_hash: "0xfdee2ad0a738d5c9a2f063b80fce04b5e61df04fd595155dc5509d45ee6495b5" - block_height: 111 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - - "0x3f0c046faed71f3fab856fcaf43a55a7865e4f8054998ea1b7b0ac4b2ec62d03" - - "0xac07404027d09239f2784ec2873ccb9baf4212a432cce6626f56e80f1e99ea5f" - deposit_root: "0x7c63909e1401449ed1e5773863d7629fbc8396468b85476a75d7e637b5f9f7a0" - deposit_count: 110 - execution_block_hash: "0xfdee2ad0a738d5c9a2f063b80fce04b5e61df04fd595155dc5509d45ee6495b5" - execution_block_height: 111 -- deposit_data: - pubkey: "0xb0933ec64b73c49071fb92028a8e3d1ad18019e177370d335fa03c61de5d01e1a7e154812f720c44109701e2b07068b0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4070a873b52b119a63ecfd68300d7f2f359963cdcdb4b7e6764647975b2fcf6ac0682c37690493d3078794ee680c0490381f85b20b8c0ff33f9642dad0064e0ab84eced4a8e38bacdcc4a068a7c5742d67e64b9019bbaf2f2a64a7e525b2198" - deposit_data_root: "0x5242358df16ac3f92030b1fbbdb91f85d477b50d2d33a2f59dcf4bce046ee698" - eth1_data: - deposit_root: "0xa277f7cb6c03e9087666ea431b99d82c13b4d5ee95ccd73b1b627bacb236486a" - deposit_count: "111" - block_hash: "0xe89d23f1eaca58026acc006eff84e317245838800cd5c1be08a8564284c75a6f" - block_height: 112 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x6cd8d51fa9ddfe114bb7f25d29576b6a72602063f2c1a541ac3e767dc0bd28f4" - - "0x3f0c046faed71f3fab856fcaf43a55a7865e4f8054998ea1b7b0ac4b2ec62d03" - - "0xac07404027d09239f2784ec2873ccb9baf4212a432cce6626f56e80f1e99ea5f" - - "0x5242358df16ac3f92030b1fbbdb91f85d477b50d2d33a2f59dcf4bce046ee698" - deposit_root: "0xa277f7cb6c03e9087666ea431b99d82c13b4d5ee95ccd73b1b627bacb236486a" - deposit_count: 111 - execution_block_hash: "0xe89d23f1eaca58026acc006eff84e317245838800cd5c1be08a8564284c75a6f" - execution_block_height: 112 -- deposit_data: - pubkey: "0x8b47707a1f563d3b1034e20be2a663587f17fece6581fca156cf660575fde4b8de4d45f1fda7ade9167b953d4c93417d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x94c71b0489c2e7cc1041a4971b92bf507c78b868d58de47fa10f673952fffa15edbf42cff7dc09f55ded92069023ed31049a7b917ac3629560e1a1ab6dd936a8e846a36cc5a691cb0cdcc1ac61a47565778aa9c8ddecb41e2cacca4fa7a9a093" - deposit_data_root: "0xecb5c27e0bd4dfba545e4be285416d20eea78a0c894c97542ead3e70cf3a0788" - eth1_data: - deposit_root: "0x98e76adf146e3608cd2e4f340d4d646e8f43a211e2326a454a86e2a2ffa8442f" - deposit_count: "112" - block_hash: "0x6803694b96704cf8b5a7b731ab9497fc091064132096166e8852ad5de1d23aa3" - block_height: 113 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - deposit_root: "0x98e76adf146e3608cd2e4f340d4d646e8f43a211e2326a454a86e2a2ffa8442f" - deposit_count: 112 - execution_block_hash: "0x6803694b96704cf8b5a7b731ab9497fc091064132096166e8852ad5de1d23aa3" - execution_block_height: 113 -- deposit_data: - pubkey: "0x8ce551755078927147bae52f683f962ca09cd68e2a14dc7444f98739fe5d27e3596314d78deedc87beb705bcf9532182" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa68afbb096250d43a72588cf6db83f6be6413f0d7350056cba7d11df6fc7efa07107dd925130c0d1ab55b510e5e39b29151649e8f371e113a83c7e2518c4a0d6d16b17c7209396a5bd9653f78f357528e277071f03bb9abfdb10580e919b84a8" - deposit_data_root: "0x54af1e6596ebc505cb6c1eb2c2a58d137f0ab4ff9e8919495631a9c6ce02e185" - eth1_data: - deposit_root: "0x28d6d95d118088e83779c9030adbbd5faebcd35e8168a1d8c3f675a80ae7aa83" - deposit_count: "113" - block_hash: "0xaaf1b5f2693fb64143ababcc4d377385cc37a7f6a87c989b5c21ee9dd1578fbf" - block_height: 114 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x54af1e6596ebc505cb6c1eb2c2a58d137f0ab4ff9e8919495631a9c6ce02e185" - deposit_root: "0x28d6d95d118088e83779c9030adbbd5faebcd35e8168a1d8c3f675a80ae7aa83" - deposit_count: 113 - execution_block_hash: "0xaaf1b5f2693fb64143ababcc4d377385cc37a7f6a87c989b5c21ee9dd1578fbf" - execution_block_height: 114 -- deposit_data: - pubkey: "0xb363a57c600a0037d54d738037358aa686e27da3ea65be95f95fc04d5736fba6338c5d544c3cf2b11262bd20e7a42dd1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8fb0e367ac8eeceeccc271b8abb3f6a51ac714bc0837d919ee3cd56df40f2b3020fa79592035cd9b4b59e428620b1fc0ef0b62804119288362814074b2247be97106821cbe2e5ee077d4935a853c17730701c0b9eafc83ff08656b8ec2fa7a2" - deposit_data_root: "0xd6fdfa51b7eb29f6327bd5f283e43b584dde088d09df4378e58b244b871c4dbe" - eth1_data: - deposit_root: "0x4f43eee61db70c0290a3ec3eeed96b98d38fa1fddb21149808760133b9f7a385" - deposit_count: "114" - block_hash: "0x9184c85f99de9e7302c25f18b814f557e5ed7363677ce9fa97dd727f1c9c01b6" - block_height: 115 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0xc2d2d3ec8e50223aa7c20032356d3d4a3be3ecfd18471318d91192e20bca4cd9" - deposit_root: "0x4f43eee61db70c0290a3ec3eeed96b98d38fa1fddb21149808760133b9f7a385" - deposit_count: 114 - execution_block_hash: "0x9184c85f99de9e7302c25f18b814f557e5ed7363677ce9fa97dd727f1c9c01b6" - execution_block_height: 115 -- deposit_data: - pubkey: "0xa5e05143d5034740cb9ad524bec81678b07223989d4534ad44ffad33ee2fc73e4ee6b297b68aef9de33f98e5487467b5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa44866b3ac0b41fb89fd7a51f4963bceab4b31e92f963aea80979a8a96835067eac75868f59c7ad204f00e117bb3cded053e6d2334e626634d0cee0120234658f7cc1e0c68f0c7d1f23327db2bf91d86b56ce7131012b0db84266bf624f0b599" - deposit_data_root: "0x72a8893e5155db8e97031c2de604a4ea3b0d3323a6a242a4253c7a539dc72ddf" - eth1_data: - deposit_root: "0xa9ddf7428ca9cb8ffa10cf3f1ac2e56066e9f962e8cef91aedd841f0c0b3a6c5" - deposit_count: "115" - block_hash: "0x0343f11c8b2cb94cf8cce1b1f0e6dafcc956a4d6ba21a0010ae204a4e50d8171" - block_height: 116 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0xc2d2d3ec8e50223aa7c20032356d3d4a3be3ecfd18471318d91192e20bca4cd9" - - "0x72a8893e5155db8e97031c2de604a4ea3b0d3323a6a242a4253c7a539dc72ddf" - deposit_root: "0xa9ddf7428ca9cb8ffa10cf3f1ac2e56066e9f962e8cef91aedd841f0c0b3a6c5" - deposit_count: 115 - execution_block_hash: "0x0343f11c8b2cb94cf8cce1b1f0e6dafcc956a4d6ba21a0010ae204a4e50d8171" - execution_block_height: 116 -- deposit_data: - pubkey: "0xaf14e8626e043caed52d9dfe62046eaa698f8b95d25cedc8c63e472def8b6a59e64febfa00e95568538c1a382ac91d2b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8a07f9a5faf428d95a6924d2e4d9aab65e9756b878d548a9ab04b57257c56c3878e1d451b36f264f6fdb1521162ccda089ad9e5c250738c983827e64dfd21b9b3662ce1c5fff45b2f73bbe47d0b1a6b208b4f2fbf78447ad5e12bf6e7a4596e" - deposit_data_root: "0xab893bbeb9c8f1b0aeb760b31be1696736b2f7ee05edff8dcc6183d76adae267" - eth1_data: - deposit_root: "0x03d68d623a18dd6af2380829bcb89709b8024f8ba83771ac3f5135bdcdee00ee" - deposit_count: "116" - block_hash: "0x73d9f83ad8851e59c41c2b25e0e568dbb0ef8c23f37a006dcade3329a8d6f0f9" - block_height: 117 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x97906a31d1ca59143dc947a1f048242f4c67382be296dcdf43a03951553464ed" - deposit_root: "0x03d68d623a18dd6af2380829bcb89709b8024f8ba83771ac3f5135bdcdee00ee" - deposit_count: 116 - execution_block_hash: "0x73d9f83ad8851e59c41c2b25e0e568dbb0ef8c23f37a006dcade3329a8d6f0f9" - execution_block_height: 117 -- deposit_data: - pubkey: "0xb41a0d9f8f19be13395aa09711b492d20eaf4a56d2360cd6daa2fd665532d852cb9224a5a39e5abff389882f961f12a6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9976e20aca195ad464d2e5bdff8f89100d8fa42357588bffae1c3ebce0af6ab593fdbb83b6e316fde942bad4dca225520e3aa856e5e5adb167c7fee7a1f022a9caaa0f199b5e5a2f7a2ecda1f016c02845f2168f0a144a1de33890ec8480441b" - deposit_data_root: "0xc970a40ac721ba0ea1adff1e2f0a10b8dc9529a25bf867f8eb22359655eb5c66" - eth1_data: - deposit_root: "0x1e0d48b0764fedc0a118aea5976361d0612144be975699f762b00c0365b22cae" - deposit_count: "117" - block_hash: "0x1f8c317ca00006c5468170bb2ad996fc5264a4622b30ff4feabf2c3a923a622b" - block_height: 118 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x97906a31d1ca59143dc947a1f048242f4c67382be296dcdf43a03951553464ed" - - "0xc970a40ac721ba0ea1adff1e2f0a10b8dc9529a25bf867f8eb22359655eb5c66" - deposit_root: "0x1e0d48b0764fedc0a118aea5976361d0612144be975699f762b00c0365b22cae" - deposit_count: 117 - execution_block_hash: "0x1f8c317ca00006c5468170bb2ad996fc5264a4622b30ff4feabf2c3a923a622b" - execution_block_height: 118 -- deposit_data: - pubkey: "0xb242e56475dca34fe92de09daee3951d647c04ed7a483a5c5c5613676f5ca88d54ec64d1aee81fb0f085aa67c88ee6db" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb0e8b1ab87c739db7149b5e2756e3fec4cb8ca9ce14cd17af4064c85e0917dfdd4c40c13f6c48cbffe760a45f63567fa13272ef95f73c5fafbfe62336292af7c1cf68d2ea3a399729e330feccc375c237c6630a031c1badee92d8d463bf2c3e4" - deposit_data_root: "0x458248ac4f1676159c16889a98e42e86fcb36615a646d1f3e8c0d56a5c5710a4" - eth1_data: - deposit_root: "0x1331f96e0caa629377f330bbad76213870a7d46ea84f70f3211344cd2864ff22" - deposit_count: "118" - block_hash: "0xa2c8860839e76bdc3c39ffe65914b4fe29936c31f0218c0ec7b1096f086e6066" - block_height: 119 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x97906a31d1ca59143dc947a1f048242f4c67382be296dcdf43a03951553464ed" - - "0xa325f304db3245c0b82c9770d2cb6ee122a7a8ee7b7d02568fcba7c3d74cd1c4" - deposit_root: "0x1331f96e0caa629377f330bbad76213870a7d46ea84f70f3211344cd2864ff22" - deposit_count: 118 - execution_block_hash: "0xa2c8860839e76bdc3c39ffe65914b4fe29936c31f0218c0ec7b1096f086e6066" - execution_block_height: 119 -- deposit_data: - pubkey: "0x894798d09babc765b3ba22473d820465e713c1d7f78f3eaeade3d957bd412a742f498a9b91e55cba8e08c36c8ad4788f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb3fe3f86d154a803bfbbcdf25da35ae7293e7a67ed8a486b2178424cf4dc4cd580419276d833bf6aebf02d5c59fb85c70e69fa7718448e6e590c5a88f81078982243ebc70eca16a8c65983d367597d9af062c7bec6e0657ed33c7caab044a6b9" - deposit_data_root: "0x84bc2d2d601a86cf3f07e155e5fbf2c6a24a838c35c6f5aeb1cf8ec9f54076a3" - eth1_data: - deposit_root: "0xc711c10ca8badf31bce9ca6b0ef21a3aad8c94218e594891dbc49e5de5ef0480" - deposit_count: "119" - block_hash: "0x09861bc26ec9b9b75d164cba54b6b8f81256b16a0b8d4fad05c0bcb2368cef02" - block_height: 120 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x97906a31d1ca59143dc947a1f048242f4c67382be296dcdf43a03951553464ed" - - "0xa325f304db3245c0b82c9770d2cb6ee122a7a8ee7b7d02568fcba7c3d74cd1c4" - - "0x84bc2d2d601a86cf3f07e155e5fbf2c6a24a838c35c6f5aeb1cf8ec9f54076a3" - deposit_root: "0xc711c10ca8badf31bce9ca6b0ef21a3aad8c94218e594891dbc49e5de5ef0480" - deposit_count: 119 - execution_block_hash: "0x09861bc26ec9b9b75d164cba54b6b8f81256b16a0b8d4fad05c0bcb2368cef02" - execution_block_height: 120 -- deposit_data: - pubkey: "0x93c65ba88f12ad22c761003cef7ffb155b9b17134ed871c0703fac60e80dbd2dd8d163bd28eba9dff88b1e9bd1ae4a76" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8358f8f27f66caf3abf2ab7ebead7028bd465266a29d6d465e94ed3bf4a26e7820379f90db0ddd4eef534a8fba0257691579607ef99b3e8cc06dd9994ac7b226a5cdf3b2e2c691ac805a26045d984fdcfdda393fdff9d27db2d8e6f58d49e364" - deposit_data_root: "0xa702f084aa633619824587a2a5aa49dbe17cdf12bfa499bcc15fb2139f2773a6" - eth1_data: - deposit_root: "0x490811983daeadcb303ff1b7ff46fd6cfcd36dd23b0ce7949b4ee1db7ad695ea" - deposit_count: "120" - block_hash: "0xa6354e31dc8b76a24c330aa6be59b256b11155ac456a21d04e4bdfd3a0e56ba9" - block_height: 121 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - deposit_root: "0x490811983daeadcb303ff1b7ff46fd6cfcd36dd23b0ce7949b4ee1db7ad695ea" - deposit_count: 120 - execution_block_hash: "0xa6354e31dc8b76a24c330aa6be59b256b11155ac456a21d04e4bdfd3a0e56ba9" - execution_block_height: 121 -- deposit_data: - pubkey: "0x8ab4d3a78c54107bd7e71a0a006cb90dec379d6d86c9b6e4b3b010ceb37236cf2566febe76f955dbf0512884215f9f86" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x80ea3f9c01a6c761102737733f9e2f0911fafe3bededf9704e219c4537de0ad1cbc4e60a67447e1d25d53c24511ba3640d7aba5362c465042b7dbf40a7a8e0a5e8c002c511c36f7a45411e5770ceefa0571a5670fdf9fd2c0b6a4ca453866f73" - deposit_data_root: "0x838301a371b334dc75bce42fbac042a519866b7fbd0741fdcacd54b584f1110a" - eth1_data: - deposit_root: "0x2d26430bf9f104f9295c2318bb2bd619f41f538ddb1407718bde5af1aa789926" - deposit_count: "121" - block_hash: "0x46004bd96b147395928f4342db995e0e5f8c84f6be8479f1713553bbfb32dc2a" - block_height: 122 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - - "0x838301a371b334dc75bce42fbac042a519866b7fbd0741fdcacd54b584f1110a" - deposit_root: "0x2d26430bf9f104f9295c2318bb2bd619f41f538ddb1407718bde5af1aa789926" - deposit_count: 121 - execution_block_hash: "0x46004bd96b147395928f4342db995e0e5f8c84f6be8479f1713553bbfb32dc2a" - execution_block_height: 122 -- deposit_data: - pubkey: "0x982d829cab4f09be252a2c57b77c166679b7e9fdf6f5cc882462b8f4dc9a90beb303c85af56304fb79b975d3643e2ed1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa49e810bb59cc1194a7ccbd85e6571d2b3fb04372071e9f520bbc6a89f17ce2b4ea4e2efdd7566736e7415c2bb2d5cae19efa75295819f670b2e1a152e8eddda18de95518f2d8ff615f131030635b766504b98a0b72e8470f476ed70c86f3ace" - deposit_data_root: "0xc8fe237e6c86c9da4429095bdfacab4dc1fd6a8145fb01abad35d9746e44ce9f" - eth1_data: - deposit_root: "0x46c5bd8627005a4b9e42e3844469187bec02232d2005a380c126d0c652cab644" - deposit_count: "122" - block_hash: "0x3472792b86ec4d8fcb0eafa369176a7c2f0836c1e258a0b136f20dc78c6fb5cc" - block_height: 123 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - - "0x7504f9b600c756a12aa0e36a3cdeb84baf7704f65c12284994facedb4cb7130d" - deposit_root: "0x46c5bd8627005a4b9e42e3844469187bec02232d2005a380c126d0c652cab644" - deposit_count: 122 - execution_block_hash: "0x3472792b86ec4d8fcb0eafa369176a7c2f0836c1e258a0b136f20dc78c6fb5cc" - execution_block_height: 123 -- deposit_data: - pubkey: "0x908ad5c41ba5fc8bea0cd8f028806a823bda814fcf6c2c32b5656c42b5d3061cfb077ecde2a50bf374e055e8d5dad4c7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb557348bd23fe03c42a128cb692187bc5b6ad278a205080fb475e6fd80ae7ab4fa5d212e3124631fb3c8ba457cb8dfc009e3909b741811b93aa5cd82d8a79e6570c1bc7e21cb62cb4540d7588b1c09803fb8a5c5994b30b8e0675567a94db604" - deposit_data_root: "0x5f3f0fad8eb6c5f9e0775ec949cdab9b768e3a35ff9f275dfba030661917ee01" - eth1_data: - deposit_root: "0x53ae75a6909e9c71ac74676e3a6d8b65f375b54444b70820da477d70eb3146d4" - deposit_count: "123" - block_hash: "0x731a443bb391702a1b35eedf368fe11f2707e192e5fa7f0c76cd4b3450486e39" - block_height: 124 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - - "0x7504f9b600c756a12aa0e36a3cdeb84baf7704f65c12284994facedb4cb7130d" - - "0x5f3f0fad8eb6c5f9e0775ec949cdab9b768e3a35ff9f275dfba030661917ee01" - deposit_root: "0x53ae75a6909e9c71ac74676e3a6d8b65f375b54444b70820da477d70eb3146d4" - deposit_count: 123 - execution_block_hash: "0x731a443bb391702a1b35eedf368fe11f2707e192e5fa7f0c76cd4b3450486e39" - execution_block_height: 124 -- deposit_data: - pubkey: "0xab4de8ffccf7b19aa6d7d4ccc4c82f091ebd5715b5dd6680edf9eb4f0dfc312e8999b89a78a8d4ed4512aec75a5e5906" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9789f3948b7b284a5082da4e971859ab6d11ebd59bba14e25a5fe9f933148d151f7e831dbbb1d2219a3548aa77b64a970b630ff661ef87608c446c8ee87eefb8728afcc9d3f062955e2059dc22cbe43120777fcc0bc080adf513864869b53a1d" - deposit_data_root: "0x5968e76b664c7a893bd1d6b4770e2ab6c367986489cb078bf9ebe15a7c37bcf0" - eth1_data: - deposit_root: "0xe61360c04f1c6070915102cc3c8e1da6f83689c00802a0a4d9421b597712b380" - deposit_count: "124" - block_hash: "0xe7d04b58be7cc09e2efaec16fc8acde80caf3dfe208a1f399464ef1e01c7c6a6" - block_height: 125 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - - "0x000a10f9b9b70f1743febe640ecb94109a63a9e80299df2f72bf1882689e6293" - deposit_root: "0xe61360c04f1c6070915102cc3c8e1da6f83689c00802a0a4d9421b597712b380" - deposit_count: 124 - execution_block_hash: "0xe7d04b58be7cc09e2efaec16fc8acde80caf3dfe208a1f399464ef1e01c7c6a6" - execution_block_height: 125 -- deposit_data: - pubkey: "0xb544d0df633f2334845f73a3921f2a716b9694baa6abcd7cedfa359ba3448029d5b874eef8b3f9f324f1ff4c0f997e97" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8fb8d58d8be318a8b649c3a74593aeb6c83698850be4571509ef5e2b053e3ace87583b89330b63a804fc56fec750be74030a530602fc80566be34a22b5a37389d036796f48619842b4b4169421c8deef8e745b2873e37fff345fedf94a395823" - deposit_data_root: "0x69c57f789da4ec1bc1c89fd375a62a159775a522bb17763f98317770d40ae9a0" - eth1_data: - deposit_root: "0x0e122e727cad3ae0d09e532df6103c967179b5f7841b6e19b9d224e2abe11a40" - deposit_count: "125" - block_hash: "0x2ac76d9635e62e48f43779725791bc2955641b541ce279dfd8a1d4ee43242664" - block_height: 126 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - - "0x000a10f9b9b70f1743febe640ecb94109a63a9e80299df2f72bf1882689e6293" - - "0x69c57f789da4ec1bc1c89fd375a62a159775a522bb17763f98317770d40ae9a0" - deposit_root: "0x0e122e727cad3ae0d09e532df6103c967179b5f7841b6e19b9d224e2abe11a40" - deposit_count: 125 - execution_block_hash: "0x2ac76d9635e62e48f43779725791bc2955641b541ce279dfd8a1d4ee43242664" - execution_block_height: 126 -- deposit_data: - pubkey: "0xab77bbaf0047e03ef4bb1ddaefb777f263c9dd556502f3078d51790653a59452f1455d23002e175ec5b541cb69007f8a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x892fdcc974b84026d23901f0772bfcaaf8ed4817e8dd529525658a3f422e8248cad7fa34e9c6d12b3203fe211340b2a90ff420761424d417a446a66c8317f8b120e89c72b7a6ea0e822bfdeaa43fc4efee6568c7f0ce860c9c8499406ba58825" - deposit_data_root: "0xae683dcb55578a08ab4cc416cbf6f9a4d400f611555ae3572f5be428049c264e" - eth1_data: - deposit_root: "0x2d75c60ef0c030b2cf8b18fa80490294476ed31bda31c346ba6c408d54bfb741" - deposit_count: "126" - block_hash: "0xea2226b57df5f18306352ff56c91e325260868a83fea4af3100d2adad1cad128" - block_height: 127 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - - "0x000a10f9b9b70f1743febe640ecb94109a63a9e80299df2f72bf1882689e6293" - - "0xeb193fa7b29a3c6c580bb7006a016eb539a1f3c2d0b529d8ca69462e48200c6b" - deposit_root: "0x2d75c60ef0c030b2cf8b18fa80490294476ed31bda31c346ba6c408d54bfb741" - deposit_count: 126 - execution_block_hash: "0xea2226b57df5f18306352ff56c91e325260868a83fea4af3100d2adad1cad128" - execution_block_height: 127 -- deposit_data: - pubkey: "0xb56c50c51aa1ba14062d9a477ae78646c459bfe12fc1fa3362f0652077a0ba090a0b780ee0b58085ad2b885fa4a37d4e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x85654b45a591e3ef272bf2d4f55ea861e6c06781b79059c0e3a56c07f86d7eef1ceae1d61cdba178078852d34103fc160a9499a05792e08759d15b414a6e174d7d01495fceda5a13517da3b020f4bbf54aadee63765fd78834ba3d6e99a003f5" - deposit_data_root: "0x1aafc611bf508823603b5049d2bf7238853a3e19edf7af77fb8fd0d4d0057214" - eth1_data: - deposit_root: "0xe9a1d8e3b5b5bce6fdcd79c03f4acb7efdd73eb1071a1974af2cfa6f02d40f91" - deposit_count: "127" - block_hash: "0xf1097ac5fc5fb25e73c35efe2e9e75acb6cbf842cded0e5912d59f67433c92e0" - block_height: 128 - snapshot: - finalized: - - "0x7a8435cc00762702eb56b993adbe7f3b4f2e490152838e57fbbd87920ffc66d2" - - "0x09a2fea2eb2470fc221e82ba026e7e9fe417bde2e54fafb68c804dc2a598cd59" - - "0x0ff1cd5c2a584c83b12d7ad77a2e309ba97738719c0876302ce0ca1e9b02892e" - - "0x750fa31940eac308088042d159ae2a9895b1c22f1b3aaf2faaaaf448dac52668" - - "0x000a10f9b9b70f1743febe640ecb94109a63a9e80299df2f72bf1882689e6293" - - "0xeb193fa7b29a3c6c580bb7006a016eb539a1f3c2d0b529d8ca69462e48200c6b" - - "0x1aafc611bf508823603b5049d2bf7238853a3e19edf7af77fb8fd0d4d0057214" - deposit_root: "0xe9a1d8e3b5b5bce6fdcd79c03f4acb7efdd73eb1071a1974af2cfa6f02d40f91" - deposit_count: 127 - execution_block_hash: "0xf1097ac5fc5fb25e73c35efe2e9e75acb6cbf842cded0e5912d59f67433c92e0" - execution_block_height: 128 -- deposit_data: - pubkey: "0x8bc5c1b16286219f479f6d00b0b31b193811b499a86139c45ff4350d8c9b492421e854cf75fba1a0dd566e6ead8ad667" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c322ba5f9cfaffd49ce18217a4351f2bff820017197a676657a1d29d140d41697adb960d340ebf075808ca553bd594713c290beac55aecdc63f96f1da3ea0945360cc8ce7a8de60186ca7a25386a09b17f01d0f3f98c084ff9f998a2ac5fbdb" - deposit_data_root: "0xfd64f7c42aff4433793cc1eceb1a97ed7e3dbd2f5588cd2c613b99fb2dd7ac03" - eth1_data: - deposit_root: "0x8aea1ce3d5cf89b67db5f909d4d50f4db94bd65fe6dba209e245d440a7723c71" - deposit_count: "128" - block_hash: "0x87ccd43a65dd5d36c0607edfaef9c6bd9d772e1b41da8140b5f93f23cf5a2c79" - block_height: 129 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - deposit_root: "0x8aea1ce3d5cf89b67db5f909d4d50f4db94bd65fe6dba209e245d440a7723c71" - deposit_count: 128 - execution_block_hash: "0x87ccd43a65dd5d36c0607edfaef9c6bd9d772e1b41da8140b5f93f23cf5a2c79" - execution_block_height: 129 -- deposit_data: - pubkey: "0xb7eaf282595bd590bde41f67783d12ccf7666aea2f1efbaeaa80c8478a157cf59ca7bf009e5a125163212b0b9f51c876" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5c648bb5cf946f279dd63efdbc85bb7567aeb488fa82fc07094fc2a06306a9bafa1eb70f96d767d1a183838866704cd18157b2e00c7c3ab7c8e97b75533ea364042359d5860aeb6aac63fc88ab600d73e66e10056da3b3149056731fe71f228" - deposit_data_root: "0xc72d087b5d20cfd70d9183225d0617b9829513337759db9764a466e391605f30" - eth1_data: - deposit_root: "0x4e1a7a0cdaa453f648dd2eee6e210e5ce1ced22668c8389b14b69cf390d06e41" - deposit_count: "129" - block_hash: "0x1daf3d0631736aff5da169b0fbfaaf76670e04bfb5f1d582847873089630a2c8" - block_height: 130 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xc72d087b5d20cfd70d9183225d0617b9829513337759db9764a466e391605f30" - deposit_root: "0x4e1a7a0cdaa453f648dd2eee6e210e5ce1ced22668c8389b14b69cf390d06e41" - deposit_count: 129 - execution_block_hash: "0x1daf3d0631736aff5da169b0fbfaaf76670e04bfb5f1d582847873089630a2c8" - execution_block_height: 130 -- deposit_data: - pubkey: "0xb404c5cda4dad57827e456beecf745b1ed9f2bf776ca0eb806010b80b8912b683c288b4f231bb67c29ddfcdeb16ca909" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89c8d47b8ef9e3282d2e9ba98c4d4cdf0fd362f210564e00a8531640fbbe574206826502ec05ea941cd31a5e591aa1b416230bf0aa78cdf74694681653c2b4e36286aa8d92d4086232d58e6b52096a526d4bbb476961d94202c63182b76b3ab0" - deposit_data_root: "0xb38f387c64b9c9322cb74515298703ce4922ef2997c1064e4429f037f1609ebf" - eth1_data: - deposit_root: "0x5399bcc9eb6e3af50e9fba6c24424fa4df66ae4a13f6b03f7d50045c7c0c434f" - deposit_count: "130" - block_hash: "0x80ecd955e11b7c0379a794762c9e233d40df9af56efee83dcade1a18cad09982" - block_height: 131 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xc712b8c0610483895c01d4680ed4421cc67ee49d0792102971ff4642b82e36c3" - deposit_root: "0x5399bcc9eb6e3af50e9fba6c24424fa4df66ae4a13f6b03f7d50045c7c0c434f" - deposit_count: 130 - execution_block_hash: "0x80ecd955e11b7c0379a794762c9e233d40df9af56efee83dcade1a18cad09982" - execution_block_height: 131 -- deposit_data: - pubkey: "0x944c4c5147a6b263898f335d2d59177c829d55901e5a4e394c9253cfbba6f0f3ce6ac393aa7b123f7a15db2909aaa37d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa46121a7594f41556fea16a1b3292312880391bce6deb315f12f445e326a2ed77b4a74007b876eaef7c8da205e00e6830bf6436dd332fa280ca379ede2c334279309527b076f1385c9e55da6319b3a534053cd4598a4979b35e136e81f7f2dda" - deposit_data_root: "0xfc30a2ec2eb7b30f13ac732834306398cda4fc65e4677ef007bec1a3d5dbf18c" - eth1_data: - deposit_root: "0x8a0505df56a0bc3b337baea7a403e6076f751267b9b0b32eb3f377a4c7464098" - deposit_count: "131" - block_hash: "0x1a51a4d2db3c6a25fac8e8df0962130e493850d808d3a798dbaa7b3cdaea01f5" - block_height: 132 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xc712b8c0610483895c01d4680ed4421cc67ee49d0792102971ff4642b82e36c3" - - "0xfc30a2ec2eb7b30f13ac732834306398cda4fc65e4677ef007bec1a3d5dbf18c" - deposit_root: "0x8a0505df56a0bc3b337baea7a403e6076f751267b9b0b32eb3f377a4c7464098" - deposit_count: 131 - execution_block_hash: "0x1a51a4d2db3c6a25fac8e8df0962130e493850d808d3a798dbaa7b3cdaea01f5" - execution_block_height: 132 -- deposit_data: - pubkey: "0x97dfc5eb14556d1a85e34c069da71fc5e1bb5e17b421d9503f25d76a8f3cd0f2f9c5a1937e785e1c0e73edb6561dd176" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x82d599849169580c5cfe93e84ecf5935ab27dc88447836eb4f9d9d1875d8d5131356e5b220d6ef92de2cc8eb93e6301c0ca1107e2b33a0cfe4a82dcb0cea523bb223e47b91eeb8fda927ac6b951d5741004e2e2340a451649b266df4dcbcb38b" - deposit_data_root: "0x0efa199eadc389a2e5876ed70eabd6fdb57b124fef39391a43197a06d4d12a9a" - eth1_data: - deposit_root: "0x6ff97ae565976870078e1d2df2f544fa9a9f52e522445e9613cbb9d6d0e0ae34" - deposit_count: "132" - block_hash: "0x5b5b8e0f913eb2cff3476e7e7cba6efc247b50f863093abe9184daa9c43ffffe" - block_height: 133 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x21919d31feaad9b4f0198aa85b8c92b9162acdb2be06e4fbb76488dce29f6409" - deposit_root: "0x6ff97ae565976870078e1d2df2f544fa9a9f52e522445e9613cbb9d6d0e0ae34" - deposit_count: 132 - execution_block_hash: "0x5b5b8e0f913eb2cff3476e7e7cba6efc247b50f863093abe9184daa9c43ffffe" - execution_block_height: 133 -- deposit_data: - pubkey: "0x9145e0920e276f19fe65b9ea81339a41dee6e21ca12512005701a014426322be4fe504f853d6ae48314902fe9ff50a43" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xafaab29ec6f5ea64ebde52ec7d3fc4ec1eca00c799f9b73b51f40a59d8047d2623c7bc92456c3754a79c0b62c722db85113cb8bdcede6b95093a591f43f63098f9d558fb898f4098d6c731534288343e6b23fe04b1b0d36e49cdc03b6a097a51" - deposit_data_root: "0x8e2528c2d652e704bf50d274aa2f813cab8038b2f0c6242556fe38e4ee1342a7" - eth1_data: - deposit_root: "0xc9c06b3a8f036dac268ac85aca78bc258b292b53bb870d6bb65d2e4400eee5a3" - deposit_count: "133" - block_hash: "0x5668a8e6259e5716d8988973626760ed0f8f381d0553230e5b74a5a9f01dcebe" - block_height: 134 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x21919d31feaad9b4f0198aa85b8c92b9162acdb2be06e4fbb76488dce29f6409" - - "0x8e2528c2d652e704bf50d274aa2f813cab8038b2f0c6242556fe38e4ee1342a7" - deposit_root: "0xc9c06b3a8f036dac268ac85aca78bc258b292b53bb870d6bb65d2e4400eee5a3" - deposit_count: 133 - execution_block_hash: "0x5668a8e6259e5716d8988973626760ed0f8f381d0553230e5b74a5a9f01dcebe" - execution_block_height: 134 -- deposit_data: - pubkey: "0x836c4b67713b082c060003d8fc839e265c1aca7f9bb82ce07f71a459d39073ebfdca87609859c70e55a1b0e7a613b395" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa04db5e044437271fd78d17b8f68970582937d883f11957506eb5a03e751ee98b10788be39acaa54ac43fdaf233d17470e7cbdaa7696e13a095e4cc2f1198e080f7d263b0e2f94f987d111ff479a24707b426aadee55e9df8aed27cf2f214c87" - deposit_data_root: "0x3a112eabe9f6e71d1909cef64d68d32ff8ceeeed9247f628babab96f83f4c748" - eth1_data: - deposit_root: "0xff0787b73d84f56a8d8c4f58ee259b2a799758380be7452d30200be34a2b1697" - deposit_count: "134" - block_hash: "0xf158ad39fac4114d21115c72d76ef1486378b8bb3972904973e3bf415093e86f" - block_height: 135 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x21919d31feaad9b4f0198aa85b8c92b9162acdb2be06e4fbb76488dce29f6409" - - "0x27ef184d8974736eab9799399f71b459bd5a849f57939017510488c06a92471a" - deposit_root: "0xff0787b73d84f56a8d8c4f58ee259b2a799758380be7452d30200be34a2b1697" - deposit_count: 134 - execution_block_hash: "0xf158ad39fac4114d21115c72d76ef1486378b8bb3972904973e3bf415093e86f" - execution_block_height: 135 -- deposit_data: - pubkey: "0xb5217af9139deb6be95a106c7651e1d8dcd8eaa04f3c6196dd52abad84c862724603686f90c7c2a985f2d75a1c8facdc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x85a96d2f00884fe1e363e875256bccc230716611daa78efc84425b051008e606401bf9b5413b9b8f806b2177cf7d1cf20cd75f895c8a69c7cb59ff553af9fcc1dbb33c360a1834daa1efb5fe57322303fabde1f93e692c96b4bbfed668ab6268" - deposit_data_root: "0xaa6054d15854b80f7ec7eaae6dee3a436a3fbc7a5b960d5a63b87cda94d45467" - eth1_data: - deposit_root: "0x450e75beff47dda09d642c64023e985bb9b4084c3ebb3fd93cee4ff6ecedca3f" - deposit_count: "135" - block_hash: "0x768e4089d2988baf80e8a20e391ae3827a2018cf543e42831949ee31d9abcb51" - block_height: 136 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x21919d31feaad9b4f0198aa85b8c92b9162acdb2be06e4fbb76488dce29f6409" - - "0x27ef184d8974736eab9799399f71b459bd5a849f57939017510488c06a92471a" - - "0xaa6054d15854b80f7ec7eaae6dee3a436a3fbc7a5b960d5a63b87cda94d45467" - deposit_root: "0x450e75beff47dda09d642c64023e985bb9b4084c3ebb3fd93cee4ff6ecedca3f" - deposit_count: 135 - execution_block_hash: "0x768e4089d2988baf80e8a20e391ae3827a2018cf543e42831949ee31d9abcb51" - execution_block_height: 136 -- deposit_data: - pubkey: "0xa38b021855057c62bac15b2de83156dc8649bad858327b10cbab68c8fa3613a3de698322826d2644652ca9ef92664cb3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa8634b7543bd89635688f7842e0202b9430332a39d6ad9670277543387708e06466c21747af713f1c6cdf62f78e96411191d7bbd079322d3c6bbf1fc4f739ca4f6a77b304566a3a77768188efde4e240fa78245d5943a3a601192112a334a76" - deposit_data_root: "0xfef7a5178631d697d14133c15287f33d6a2ccb3e6efe5dd46022965eca0f23fb" - eth1_data: - deposit_root: "0x9825f4b4b017dde1e6fda6ebb8a809409d57cc060bf19d44a9a898a26f75d50e" - deposit_count: "136" - block_hash: "0x051c8ee04d7535b585729a7ead7016d35ccd323e274e39a000a4d302bf454de5" - block_height: 137 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - deposit_root: "0x9825f4b4b017dde1e6fda6ebb8a809409d57cc060bf19d44a9a898a26f75d50e" - deposit_count: 136 - execution_block_hash: "0x051c8ee04d7535b585729a7ead7016d35ccd323e274e39a000a4d302bf454de5" - execution_block_height: 137 -- deposit_data: - pubkey: "0x828b5be17d71a278644b6fbe7ab5fd3a065312d1b03734e0b9d74703a566dc99815c81fc50b13725961376edc2f54405" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89235bcafefc07c6ed61bc483071b76d22e0881272925d61d8178942b701797d907ad10a3ef967df5f647863540e70d6052ba3814abffac5acb8bcadeb41b9c393dd2d664871535e6984725c7cdac5c1433abb7e05f06adcf75fd2c3a8e096f3" - deposit_data_root: "0xae3d1bd187858e08287ed35692a78547ca791719a1cab975707b4c4c023d1f12" - eth1_data: - deposit_root: "0xf52b85738cb675d5d8f894f5653ed2e05cf513faae7dbec06e9b1841510d3bdc" - deposit_count: "137" - block_hash: "0x0bfaadbce1850c4917a8e7b161d3c926ce11488ae996ade89332e76c3c26e30b" - block_height: 138 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - - "0xae3d1bd187858e08287ed35692a78547ca791719a1cab975707b4c4c023d1f12" - deposit_root: "0xf52b85738cb675d5d8f894f5653ed2e05cf513faae7dbec06e9b1841510d3bdc" - deposit_count: 137 - execution_block_hash: "0x0bfaadbce1850c4917a8e7b161d3c926ce11488ae996ade89332e76c3c26e30b" - execution_block_height: 138 -- deposit_data: - pubkey: "0x956aeb449c6e00e75a7795ea552bb0a2c14e065bfc9fee78c5a337f9d0c1814045802ad4f2e3c60868e54cd381809cca" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x819db83518bfe3262416f3592dc4a98a7811decc1c2e3b75adbec61e503cf31adc93bb0ab7167f5cb5659a4836dbb047141c61f810b392c68f8abba9699131fef029ad3ed054895b35c599460a3c3c25a9c0290bd3d4a51806cacef247589af9" - deposit_data_root: "0x7863ebf9d15b7d7faf3eaa9c5921396b467c452ae27d2a17cf873fd03ebeb5b4" - eth1_data: - deposit_root: "0xa5000d5a716be585c7263959af46459fe207ccfee4b131e340db755d7841b6e3" - deposit_count: "138" - block_hash: "0x7e3ff3c5214e2e9d1bf033fbd58772d2df71241ff00f285e467559ba2c435729" - block_height: 139 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - - "0x4e197bcd1ccf6d1ef8b4b3c658d14cb45b72045390d6afc86479c562d99290ee" - deposit_root: "0xa5000d5a716be585c7263959af46459fe207ccfee4b131e340db755d7841b6e3" - deposit_count: 138 - execution_block_hash: "0x7e3ff3c5214e2e9d1bf033fbd58772d2df71241ff00f285e467559ba2c435729" - execution_block_height: 139 -- deposit_data: - pubkey: "0xa0ab6917fd4c65ff95b1a5ed5f3c0d7cef103de58a90f2cc5383b4914566aa085f04e8505c862a29a0c914072746f83a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b99987e93032d8d9d07d726957fe4394b818ee34dec214aa6dcdb1469f0ed7aa0dfeb4ed805baf48ef287f97b9058140262f05105769e65decbac6d3df02f712cfb0ad7a2ff65eba13fcc167d94d993f8c3148143731d4eb028ec23902443a9" - deposit_data_root: "0x4fc27de5f8c8d68da0bb0258d0151521fbac62e4967c08b8876cd976ba113e59" - eth1_data: - deposit_root: "0x270aab20e630999f0ebe072b5f955d7d0b6f0919157002eab351eef7873a9b1a" - deposit_count: "139" - block_hash: "0x294cb83538800d76ddc52bd16c33ff956de7f5b94d9419178b50824aefeed6b5" - block_height: 140 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - - "0x4e197bcd1ccf6d1ef8b4b3c658d14cb45b72045390d6afc86479c562d99290ee" - - "0x4fc27de5f8c8d68da0bb0258d0151521fbac62e4967c08b8876cd976ba113e59" - deposit_root: "0x270aab20e630999f0ebe072b5f955d7d0b6f0919157002eab351eef7873a9b1a" - deposit_count: 139 - execution_block_hash: "0x294cb83538800d76ddc52bd16c33ff956de7f5b94d9419178b50824aefeed6b5" - execution_block_height: 140 -- deposit_data: - pubkey: "0x93e08f94b3c1e5e9e7454185c5493111db66673fa0f1ba86d7a395858a32fd2c2ccd0c4838affb453112f0e9a8e3a370" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xadf8d364f8bfd7352b5423ec6e9891d9dda14132bb5fca8ec977b3eebda7df0e67a3dbf69e7c470714ef28028cb4190b18c5c998b8ec2d65089e4f1e89ef078c3b3d20f8a1c223a4d19cf7b6fe94be511c6633da6d64f2c60148926bb05f284f" - deposit_data_root: "0x394d27e70621db71ab9db062b080a827591c330679e0db882e6086d1ce0004df" - eth1_data: - deposit_root: "0x6a884aaac611577d4a3e6b1beda285d44e45c7908553df6142fcf83dfb30dfe2" - deposit_count: "140" - block_hash: "0x2ce6dbcabe45faa4be2c1f491500699fab5011beb8f8edfa6125188d5c75f474" - block_height: 141 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - - "0x94db8bffb5c1311bba8a0713a8cb1ae95ce40c9b0dc482ffc6273482df8a11eb" - deposit_root: "0x6a884aaac611577d4a3e6b1beda285d44e45c7908553df6142fcf83dfb30dfe2" - deposit_count: 140 - execution_block_hash: "0x2ce6dbcabe45faa4be2c1f491500699fab5011beb8f8edfa6125188d5c75f474" - execution_block_height: 141 -- deposit_data: - pubkey: "0x907c4f53d28167c96d711746d338b7428e7ffa389ec76497920c25445df3b1ce7e88648a5fa9f4e1b5d2d254938bb65a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb718c98631b6896423bc477b0c6b6c38aad77af35657008c27a760225c17f1d83244c9e67b031d1ff54696774c9e03a70397a5377b9ee1a4f05b4086659fefcad9e8e8992feefa489b51e35063789424dda7cef6c87a5a52ffedd58f87e9e1dc" - deposit_data_root: "0x27c015fcfe5104cd1c8d15aefb823bde65e61ea352c3b2d2886eb671caa5e700" - eth1_data: - deposit_root: "0x34867700acac7b229aa98c88283ca0967e781d7640ffe43521d8f32a43b44521" - deposit_count: "141" - block_hash: "0xdd2ca2e628c5fe5f8e641e57a076568b40c0a85522cc2e0860f38eeff295e68f" - block_height: 142 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - - "0x94db8bffb5c1311bba8a0713a8cb1ae95ce40c9b0dc482ffc6273482df8a11eb" - - "0x27c015fcfe5104cd1c8d15aefb823bde65e61ea352c3b2d2886eb671caa5e700" - deposit_root: "0x34867700acac7b229aa98c88283ca0967e781d7640ffe43521d8f32a43b44521" - deposit_count: 141 - execution_block_hash: "0xdd2ca2e628c5fe5f8e641e57a076568b40c0a85522cc2e0860f38eeff295e68f" - execution_block_height: 142 -- deposit_data: - pubkey: "0x987b620dd2ade22c44ac3a642d17ac9009da6c2d989028957da877e5178668216cb9ad2314a520ebeeb8b032614ca2f7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5294d4bad5d63db39d236813cbc1032093b8c7649d28b4b2515c03855f56d78ee8b243e6b29935d83d6a9ce344d2ae618894c91dd7cd82eebe2020938f55ee77b95118ecfc2a80792b23e75251396cb09edad531138fc2129d3442934472fea" - deposit_data_root: "0x55bcef5f8cc010243428b8c8d96fa0cade22f5df82fdd813c337482189f7d33e" - eth1_data: - deposit_root: "0x6343479ad51df7360082d3c7736a119a9cf461dfe40f749490b98cd60c48b1d8" - deposit_count: "142" - block_hash: "0x3fdc183d26e681a76cb366df8540c48422832c01893da752c20c02764f3e42ef" - block_height: 143 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - - "0x94db8bffb5c1311bba8a0713a8cb1ae95ce40c9b0dc482ffc6273482df8a11eb" - - "0x61ecb3ba1163a8bd70a0c91060a0d7df5c5f1d429222c6da2628d42f51140b8b" - deposit_root: "0x6343479ad51df7360082d3c7736a119a9cf461dfe40f749490b98cd60c48b1d8" - deposit_count: 142 - execution_block_hash: "0x3fdc183d26e681a76cb366df8540c48422832c01893da752c20c02764f3e42ef" - execution_block_height: 143 -- deposit_data: - pubkey: "0xb4bb3db19f9162fb238ddbdbf5b8e819696e90783775249825d64767625f2b6e9c52edd859bf8afac8a87371e9100d24" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xadbff5ad606002d50ac669f878194974fb3f338b8301fb46426e92b4e65309b1eabf85441ddd75dadd6eec5293ee05df0860040dbf875e698cb23cadd960b8eb23674a86795bca545385e8b03b847ab4ac7970a6b2a73b960e54866b11fbd1ab" - deposit_data_root: "0x79b12382ebfde996e7dc957b826c432d14eb7f3a3b61c61800707f60c4bf193a" - eth1_data: - deposit_root: "0x4095812eaf8df557e3e6c36dd16b99952769d623281342926d22b6d07d270553" - deposit_count: "143" - block_hash: "0xc782ae505c8831621b6d9afd7fff61d359f1d39248aae61504d561695fbb8d10" - block_height: 144 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x52228e1e54f9b75dbb4c3e67d77e36c395d945cb6a4b5494c18eb1218db1ebb2" - - "0x94db8bffb5c1311bba8a0713a8cb1ae95ce40c9b0dc482ffc6273482df8a11eb" - - "0x61ecb3ba1163a8bd70a0c91060a0d7df5c5f1d429222c6da2628d42f51140b8b" - - "0x79b12382ebfde996e7dc957b826c432d14eb7f3a3b61c61800707f60c4bf193a" - deposit_root: "0x4095812eaf8df557e3e6c36dd16b99952769d623281342926d22b6d07d270553" - deposit_count: 143 - execution_block_hash: "0xc782ae505c8831621b6d9afd7fff61d359f1d39248aae61504d561695fbb8d10" - execution_block_height: 144 -- deposit_data: - pubkey: "0xaa083a83471b7938693e54b673d98f90340fe5cd2556b27eeb9c9069b7150e853391d47543dab155f6fdc8ab7f2e185a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaf55c3b8d00ede905a7e5a546aef5e817f540950cfcb04fd86a16f4574897c7118d1a971cb06018ea5a8d59b673a6e19060462b839135255e8790d9c2152fe2497f090027242091f0fa19d957524046f920b4e980631216d9ad88ffae00894ab" - deposit_data_root: "0x62f0c56109eaf759c780578e7fa47725568ac9c4b417a79f75e0778b98f8dac6" - eth1_data: - deposit_root: "0xaab06426f70faa1c4f596a4f42f15dcfff709218d7f4c479835e15d0f7612f82" - deposit_count: "144" - block_hash: "0xa8bd5909dae96b248a94193373f3ebc4050994aee8ec82e5eaf61530e66ba126" - block_height: 145 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - deposit_root: "0xaab06426f70faa1c4f596a4f42f15dcfff709218d7f4c479835e15d0f7612f82" - deposit_count: 144 - execution_block_hash: "0xa8bd5909dae96b248a94193373f3ebc4050994aee8ec82e5eaf61530e66ba126" - execution_block_height: 145 -- deposit_data: - pubkey: "0x916d306c24956c1a97678695330d240cb492062889dbfbaa6349cf53259c719ef83748b065e4a30fa6edf8a171af326b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c8963319e8e7e3dab0c8048410b8818fa648f4a4e337d06d2226e5732ce99e8089103886812055eee70315a028ea5b3044691168068d150967f6dd432e585fdfc9ac2d5b593add5e671ec6a1ef1fecf265fbe43f564b1d63ca3ed6dc1d1d49d" - deposit_data_root: "0xb779d0910d0c595a2fe92c63c2e8882f2e0db6c55f749864cda06e9ab53a1175" - eth1_data: - deposit_root: "0xd41b2fb56eabbe8a5494e16f53677b85e8329a31bc1b106c57e0d67ec4f08c2b" - deposit_count: "145" - block_hash: "0x59e61e4cbfabe9ebff9d7632bd4946b9b34b40bd189866c9d3a300f7e88584ff" - block_height: 146 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb779d0910d0c595a2fe92c63c2e8882f2e0db6c55f749864cda06e9ab53a1175" - deposit_root: "0xd41b2fb56eabbe8a5494e16f53677b85e8329a31bc1b106c57e0d67ec4f08c2b" - deposit_count: 145 - execution_block_hash: "0x59e61e4cbfabe9ebff9d7632bd4946b9b34b40bd189866c9d3a300f7e88584ff" - execution_block_height: 146 -- deposit_data: - pubkey: "0x89d9aef34711c5ea0787f591e4683f34727391729c0a402702a51de4d6a36a9324e1c77890a1b34c70a06d30bf9cb0c9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa558bdece7dc28e7a68298edb2b4d9277e21c590ec58c8e9cbf85560ce442eb91b5d35be159961dded127f612a232bff0b8df1043e6d592a498d2f672032a443e3a235337b2aeb596cc24bd20aab9001357399c450dd911107e6860e061bc6b2" - deposit_data_root: "0x952e03098c3a3e617b2085b2f360883902c7560615ef16f06ad87aeef11d4b13" - eth1_data: - deposit_root: "0x42a6aa131d749068ece539bb5dd92c0b35abadaf3fdd09e6dbbfbe3a9864a4de" - deposit_count: "146" - block_hash: "0xdbdcb5286172a0827e2cf09afb3ec1a5edf6210eceed78570a82ec9b985793ef" - block_height: 147 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xfcb2cabbd2e78c7299e4090c745a5f70f964f9609e16ef7dbf822ec6be390b5a" - deposit_root: "0x42a6aa131d749068ece539bb5dd92c0b35abadaf3fdd09e6dbbfbe3a9864a4de" - deposit_count: 146 - execution_block_hash: "0xdbdcb5286172a0827e2cf09afb3ec1a5edf6210eceed78570a82ec9b985793ef" - execution_block_height: 147 -- deposit_data: - pubkey: "0xa3cc6919919abf050a3e64b6c5d826148ee3f766e6b67e7e8000645e51ebed1b9c6a20b9b7413a4eb835529cbe4f77a9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5eebafdde0dcf96d9ed536932dac3368f11890a1c9047ce33777dd864b8edb96d627c643dc10a1ad25704bdc94a10d81163c449b7cb1298523fe6817f78d6c99a89bea5c444452057a26a8002b66129b895f24ca1e5594b07fcff0734df654d" - deposit_data_root: "0x416f86acf71961090832d765a7a736ecf18cbb6a1fd9f1e42ba0ba99abcc5a1c" - eth1_data: - deposit_root: "0xd0c5fe81027e3262bf1682a130fb3952371006f707a02c98cef95952e116b566" - deposit_count: "147" - block_hash: "0x762506294eb00ae05c84543d9b495e2b0b24047aa0ff1d747ff474498d1176eb" - block_height: 148 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xfcb2cabbd2e78c7299e4090c745a5f70f964f9609e16ef7dbf822ec6be390b5a" - - "0x416f86acf71961090832d765a7a736ecf18cbb6a1fd9f1e42ba0ba99abcc5a1c" - deposit_root: "0xd0c5fe81027e3262bf1682a130fb3952371006f707a02c98cef95952e116b566" - deposit_count: 147 - execution_block_hash: "0x762506294eb00ae05c84543d9b495e2b0b24047aa0ff1d747ff474498d1176eb" - execution_block_height: 148 -- deposit_data: - pubkey: "0xaa70cfdc554a8e67bbd3e6f3d2a0ef61c2a7ce1784acef01e9d7f08ee3a4723c2b7bd789c4bb687f19466a58e6e7bb34" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x937471d746fdc9d2bdc43b70133bf53b8c934685e3a220b02d6615648ff1c2d17afe93e5e8844825a1e71e30269e703d02cf89a01ea5737c58964ecbb42ec4736721f66cf549231b1baf839ad3775665ee3db8f5af6c7abc76fe4985e881f3d9" - deposit_data_root: "0x11fa92b165b8cdd90432e623dd16b219357f7258304be89741c3748fc2b07281" - eth1_data: - deposit_root: "0xabbeb3c7c12ac8ea30a18ca7f4953f788c6d7b6d28375d597ab8188ae4750442" - deposit_count: "148" - block_hash: "0x49319e6b9bd5b42ee760b78a016ff8c3472c40c0aa015f6f64c26f8ec9b73da4" - block_height: 149 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0x2f36ad9e26bd49169087381023a518ca363e37b699bea22fb07fe8fc2da3721a" - deposit_root: "0xabbeb3c7c12ac8ea30a18ca7f4953f788c6d7b6d28375d597ab8188ae4750442" - deposit_count: 148 - execution_block_hash: "0x49319e6b9bd5b42ee760b78a016ff8c3472c40c0aa015f6f64c26f8ec9b73da4" - execution_block_height: 149 -- deposit_data: - pubkey: "0xa1a1b99827c25c1079d4ed035a31478a38c2141db49291d0fcd10b64eb6ee5b0d9e758a9b47f40b2092f1c150bc28e11" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa843d0ca06dc76c42f57ccc9a5f50b5c17f39135ed33d3eef9a9b88826c3cf5dae2e1991f40163184ed4cf375d37ac2f147c75eb8b4e3790ff510008c0960eec1c5bca5af9c7e4ddf81277acdfe4f264cbd5d1cc1608b0f3db2fe000e2edb789" - deposit_data_root: "0x1bc04fb54834e25adfacf21f8408527b4ada1fb47d5355cf0024436cadb5df12" - eth1_data: - deposit_root: "0x4165f4725bc2e58cef28e6a287ba3fd6bf46f445fc65b67d383d7d6b7bf709cd" - deposit_count: "149" - block_hash: "0xc5a9ec49e34f270b82dd423e53c924b72387fe2987f8b0ba039efaea92a57b63" - block_height: 150 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0x2f36ad9e26bd49169087381023a518ca363e37b699bea22fb07fe8fc2da3721a" - - "0x1bc04fb54834e25adfacf21f8408527b4ada1fb47d5355cf0024436cadb5df12" - deposit_root: "0x4165f4725bc2e58cef28e6a287ba3fd6bf46f445fc65b67d383d7d6b7bf709cd" - deposit_count: 149 - execution_block_hash: "0xc5a9ec49e34f270b82dd423e53c924b72387fe2987f8b0ba039efaea92a57b63" - execution_block_height: 150 -- deposit_data: - pubkey: "0x87af7702ff5e6e9a4416bbb516c3aeec7827408b75e3d1a8d420031157ba7a5a4d1eb565d29f100c5cdaddc05399bce1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xad97e9253436103fbb79067fea41d3db0b36abbbf2c9a345ac838a16b93ebc6fa0414bbe4c96514c67dfb5e4d207df200bb413b4ea1be94dbaf9d008c49aeb2fdd0552dbcf171e0f8a6fa89bfb6139d606b5ed0765b4047c489999bcbac53c24" - deposit_data_root: "0x1d331b2e13e8ff1333f00c80b5de05a294bfeb904d782e6636461a4ec01cca24" - eth1_data: - deposit_root: "0x5f04ee7b040e36e61c3abf826d86c178cadd08541f545298c02d6fcfe7defae5" - deposit_count: "150" - block_hash: "0xe44b8c19d80353eaaebdbad5689d94f2c68ce2977dd2c687ee64560e75bd46e4" - block_height: 151 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0x2f36ad9e26bd49169087381023a518ca363e37b699bea22fb07fe8fc2da3721a" - - "0xdecd5c32fc1a5a55f4b2dff4cefc1b493884d7c78f74b3d9c399f3a583ba4aa8" - deposit_root: "0x5f04ee7b040e36e61c3abf826d86c178cadd08541f545298c02d6fcfe7defae5" - deposit_count: 150 - execution_block_hash: "0xe44b8c19d80353eaaebdbad5689d94f2c68ce2977dd2c687ee64560e75bd46e4" - execution_block_height: 151 -- deposit_data: - pubkey: "0x8db57d195b1216309f3182f522ee9c6a724af5eebfc8faf058edb4e444a74f7ca9fb0f227a7960887abf8ec4697ef4d2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xac2c9dfc4a9bfb53a71b7647109bcf478924d240e2501655e30e4fa4db22bd7f004513f5600dc19cd66141ecd1ff775904b8085d23885c3a414fb8883bd7676b32f008dd84beb2e32ff444a9e7c047cb2884ff998644aa5ffde04acb7a1a952a" - deposit_data_root: "0x664b99054c780ab51854845d95d8ccad24d1f9a45fbadf4f04664f9e533da249" - eth1_data: - deposit_root: "0x1bece2bb88b659bbafbd110cbc8d8765c5488f30389cc03a4b48730f8324f501" - deposit_count: "151" - block_hash: "0xc5ed7232fa2461e41d333f13bfd2e3e7f113aef12fe4e465f1c9ad345f140679" - block_height: 152 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0x2f36ad9e26bd49169087381023a518ca363e37b699bea22fb07fe8fc2da3721a" - - "0xdecd5c32fc1a5a55f4b2dff4cefc1b493884d7c78f74b3d9c399f3a583ba4aa8" - - "0x664b99054c780ab51854845d95d8ccad24d1f9a45fbadf4f04664f9e533da249" - deposit_root: "0x1bece2bb88b659bbafbd110cbc8d8765c5488f30389cc03a4b48730f8324f501" - deposit_count: 151 - execution_block_hash: "0xc5ed7232fa2461e41d333f13bfd2e3e7f113aef12fe4e465f1c9ad345f140679" - execution_block_height: 152 -- deposit_data: - pubkey: "0x8cd26495562e8fa526dd3dd5ccf7706e0b802747a2858ca76e4be7e9188ecaaf095b7ba58cf504057c4039e990f88618" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8370ace5a4fcd6231c5bab72652d4f508a407e5d7b177ff034ca82a1efd0d06b23b618f17268d7ab67f5a964beec59e0d929c6b611092e67099be102b77782d704d63da4e6137bdff977eed1e526332624e5c052bfa16e71b9296ffd5f4e1ad" - deposit_data_root: "0x82ae1b44b3e9bdcf2b48fdf000acbfbb762153634aac9d5ed5663bd6fe57ddd5" - eth1_data: - deposit_root: "0xa0a5548e34617b4b07307f5e80a50b88320c59ad0b655a8b3d01f7e77b96cedc" - deposit_count: "152" - block_hash: "0x42f0b6cc26d7613b3658fa113804dfc7624a3672887ca41229f9000a5f1eade3" - block_height: 153 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - deposit_root: "0xa0a5548e34617b4b07307f5e80a50b88320c59ad0b655a8b3d01f7e77b96cedc" - deposit_count: 152 - execution_block_hash: "0x42f0b6cc26d7613b3658fa113804dfc7624a3672887ca41229f9000a5f1eade3" - execution_block_height: 153 -- deposit_data: - pubkey: "0x95d668e777610672265275332a570af04c1a6090d9caae5152b66d476a7ac895c120e68724bdf30d3a51ece24a76b225" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb0bd20a856159c8ce771848cc33ca90aebe6bd56f8537a2ceed4a3a043cb448d489342b299bfcca3333a3f10c7c483780b335c78197a348eb2d0d28cf16e2a50368478693799d9b46076fbae6981732deacabac0bfe9747c5642477fa3718e59" - deposit_data_root: "0xf952d77f6747501c8f12ac319fc9b537ffeb20e38afc5ff9046521c27076607a" - eth1_data: - deposit_root: "0x77bd65842bb2ab1513449bbe102333c0bd621424fde5d37257a32c3d26eaeb5f" - deposit_count: "153" - block_hash: "0x9af33652ee1b895df878aec4adb356e1bee86181c4c6117a1bf4ad21c1258669" - block_height: 154 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - - "0xf952d77f6747501c8f12ac319fc9b537ffeb20e38afc5ff9046521c27076607a" - deposit_root: "0x77bd65842bb2ab1513449bbe102333c0bd621424fde5d37257a32c3d26eaeb5f" - deposit_count: 153 - execution_block_hash: "0x9af33652ee1b895df878aec4adb356e1bee86181c4c6117a1bf4ad21c1258669" - execution_block_height: 154 -- deposit_data: - pubkey: "0xb37c32301c15cf9a62fdf10ac221d751918f78ca95cf7f79b5a3828fe77c88561cdf863454133bc4bf56e6209b53d0d8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8938e16203e369321f5e88d883f7ee521005230d74981fc6ccde25b775982cefbe88888ca720bfb48b2f77aed3d849221874fb97dde7bbe699cb17b397bdf72fbe580302fdcde9dff66c2567a1c518c63eb4a923619918ff18e76167c184c017" - deposit_data_root: "0xbcb802dd617c023460f14a9e490207b829016a9b88cbb44b87fbd9fed9a00452" - eth1_data: - deposit_root: "0xece431b6b5a2d074c528e73dd8a1fc686e167dcf3575e118deb1c012e2beba56" - deposit_count: "154" - block_hash: "0xacd9c61ebeadae453909aac86b39107821848a4c7e85e7ae99fad8f46c09c5dd" - block_height: 155 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - - "0x11665987a6e900976d175d96c7ec5ddf750a00e89237cba75d596b7355553279" - deposit_root: "0xece431b6b5a2d074c528e73dd8a1fc686e167dcf3575e118deb1c012e2beba56" - deposit_count: 154 - execution_block_hash: "0xacd9c61ebeadae453909aac86b39107821848a4c7e85e7ae99fad8f46c09c5dd" - execution_block_height: 155 -- deposit_data: - pubkey: "0xb923cab7abb3e0b5a8ca7b841662262014e59dd8ab24ef4513ef5fc1c85dc1860bf4fee2565a732a7df4dd73ff638403" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89fb7bcb413178f88f843949a819180630d0651d184fea6bbba521e241c52834e9c9b1ab7496d6791f99b3cd4c0d23cc15b3ccb9893badbc524e1e67b027961c3d43661348c6a938c87f573d0f59919db632ef38279b28e03593e4bb9d1587bd" - deposit_data_root: "0x0c4afbd64a8f690f413bb4eb4ba458f8af3c57cb27ef5043155e5ae329db963b" - eth1_data: - deposit_root: "0x5699749c403acccf444931ff8920ea52316ce8ba01b47566db967ac82998be4b" - deposit_count: "155" - block_hash: "0x5b56da2713e1546d90efde2fce7553310399d03774f4495e3ddfab74615c91e1" - block_height: 156 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - - "0x11665987a6e900976d175d96c7ec5ddf750a00e89237cba75d596b7355553279" - - "0x0c4afbd64a8f690f413bb4eb4ba458f8af3c57cb27ef5043155e5ae329db963b" - deposit_root: "0x5699749c403acccf444931ff8920ea52316ce8ba01b47566db967ac82998be4b" - deposit_count: 155 - execution_block_hash: "0x5b56da2713e1546d90efde2fce7553310399d03774f4495e3ddfab74615c91e1" - execution_block_height: 156 -- deposit_data: - pubkey: "0xa25d1dd7f5dc5ed5aaba0187d33ee72921d6455b6052c657d87e108aaeee9c31c53701e7b288ae0f9ca74cae34a1f49c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x918558e2fb2ed8d33bc35cbb6794dce54fdc363d07eb66d2b8def16d8ff28be953d0d2b9d4207e6f1b4157aecfba99810472a8ad6fcb41a6f968a83c82cb140ba8bf1bb947f742ad470f4f3b83d447e38b925578e07f421c6a518a57b23dc84c" - deposit_data_root: "0x069c4690e4914d5b8fb41fbd74c9205c5ef329affcaf903ec8293ccbf3943bcf" - eth1_data: - deposit_root: "0xf287114b93614b301a10c43ed09504c14298377eef05000f902e4cc5ec7daef6" - deposit_count: "156" - block_hash: "0x9bdce9e95c9fb72c2018eddd357e19ad47107c224bae328531bdd0716e7e500f" - block_height: 157 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - - "0x5cf0c39a7fa083994f7baf809c387f9ab82024f5f1bb1e9badc5d3b26c5c2973" - deposit_root: "0xf287114b93614b301a10c43ed09504c14298377eef05000f902e4cc5ec7daef6" - deposit_count: 156 - execution_block_hash: "0x9bdce9e95c9fb72c2018eddd357e19ad47107c224bae328531bdd0716e7e500f" - execution_block_height: 157 -- deposit_data: - pubkey: "0x824904d20a5620ca46c015ff630e1e26fead9df53243354ace937f01a971916e8883687a8e5f087598c633a91d0d6fbd" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb238a8b1da5862bfce25d25b502130c2809263d257efdf8eef3da05a7b75b70f463f57a7c14e21b5853dfb404a6e3e66017f1d987fb634500fe403fe0ee06f0a329108af069c05b429dfc2b0ef87d7a384dfd10ed0d7b0ab82d335ed33bef84d" - deposit_data_root: "0x1147a26009ddb6d159aad698f440619d7503b848807e09679dcbd58bf1b3a899" - eth1_data: - deposit_root: "0xc7307a5a78027139f9a0fcb9ec77193528b7067f2468b26ba79f6155fa579205" - deposit_count: "157" - block_hash: "0x1f02f863183c9f6f57bf3146745f7461290f64f73065bcd52e065e97ca1f4ecb" - block_height: 158 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - - "0x5cf0c39a7fa083994f7baf809c387f9ab82024f5f1bb1e9badc5d3b26c5c2973" - - "0x1147a26009ddb6d159aad698f440619d7503b848807e09679dcbd58bf1b3a899" - deposit_root: "0xc7307a5a78027139f9a0fcb9ec77193528b7067f2468b26ba79f6155fa579205" - deposit_count: 157 - execution_block_hash: "0x1f02f863183c9f6f57bf3146745f7461290f64f73065bcd52e065e97ca1f4ecb" - execution_block_height: 158 -- deposit_data: - pubkey: "0xaed2a3ef693d13698e77966b8125442ac40ee0a62e8d97f71493c966da3c8604932dcee09606c2394afed25f8ad4f31c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8e2b32b5180bba930cd1d405258ec2cd4a7dcc0bba374a4da4e57ba6084db5341db349348413d6385fd336cd476eb1e41396bfb8201727239547f3f175b8baa7b0c98de9aca97fe7226a5fc354e7d16c39275ac3ce5bd36f3fa5ffbbbba1e4ab" - deposit_data_root: "0x1e35604482803af5afed89b925dcb5e9f41ab291388918e7b317d467e8bf2aa8" - eth1_data: - deposit_root: "0x6858813a05b486da3ccdcc7c09b56d5a96b1396a594ecf561ca209e7273d2153" - deposit_count: "158" - block_hash: "0xd52fe7ca49fb3425010be4c5fc698606361cdbecd68ef63800d013391b53939c" - block_height: 159 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - - "0x5cf0c39a7fa083994f7baf809c387f9ab82024f5f1bb1e9badc5d3b26c5c2973" - - "0x431fb43239425b2f4faf58bbfc2e8bf64202d513954ef412ee9154052f667c4b" - deposit_root: "0x6858813a05b486da3ccdcc7c09b56d5a96b1396a594ecf561ca209e7273d2153" - deposit_count: 158 - execution_block_hash: "0xd52fe7ca49fb3425010be4c5fc698606361cdbecd68ef63800d013391b53939c" - execution_block_height: 159 -- deposit_data: - pubkey: "0x8f8ac057107bc490de273453730753b9e2b69df03917a0addbfb13c5152d93fa05702cf21d8b58ed7c08ac3295c1de3e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb799bc548db09f68b31bc25159d865b5925ace14d63cfa21af51f8e822d85b6221ad968839f1bc57e8218e5a8c093fe6030098d5c8c43a59bea334048bedcc511b3ffb6c1d64876d0214238f70b27267e606afb1747423b1aa254209bea36e8e" - deposit_data_root: "0xcc6561589f174b04b6adcf0b2a893667c65f3443bed0e5c8a3af84d3157c3baf" - eth1_data: - deposit_root: "0x28bfff0f74194d7bfcc9ba76c21cfe0c2908f605b8017e023792ece9d9b1c1eb" - deposit_count: "159" - block_hash: "0xe93ddeb537374b4dbb453dc08411c26a02c36c9d3b77f4b9679a89bd75b70dae" - block_height: 160 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xaf12585229c24dfcac5f5fa3ac66011dabdfd39529f6d98c07abfa998c1b4e52" - - "0xb691ba9f86444f0a58a7e0becd7c3bad9b44e5bf6a88b85a7cfc6f243f6e2fb8" - - "0x5cf0c39a7fa083994f7baf809c387f9ab82024f5f1bb1e9badc5d3b26c5c2973" - - "0x431fb43239425b2f4faf58bbfc2e8bf64202d513954ef412ee9154052f667c4b" - - "0xcc6561589f174b04b6adcf0b2a893667c65f3443bed0e5c8a3af84d3157c3baf" - deposit_root: "0x28bfff0f74194d7bfcc9ba76c21cfe0c2908f605b8017e023792ece9d9b1c1eb" - deposit_count: 159 - execution_block_hash: "0xe93ddeb537374b4dbb453dc08411c26a02c36c9d3b77f4b9679a89bd75b70dae" - execution_block_height: 160 -- deposit_data: - pubkey: "0x8fe63d0f0da14a975a69446571eaa08409b6b4d091c720ca26519156a1cbd9e0fd44de574d8486ff98ad4086e0d96f59" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa80cc551d9eb7d869858e173eb701e706fa8bb1fd82e798bf19085c1b07d1b4807681fa0fa34ab3f802ddb664933a43513a14975a33d720b949c4873ce646f8f80ea6e8a645e404c81441b7ef30076650694972b8cbc3ff2062ff4772dca91c2" - deposit_data_root: "0x59822146ece6a1e35fa5c3b0b38f1a7ccfb6cea652ae7d6d6867a0cd2298276c" - eth1_data: - deposit_root: "0xf125f86aaf7df54e79f43355d6be50d55daab4addaaabae4c58e7a977f3b1cc2" - deposit_count: "160" - block_hash: "0xb6bbba5e6a9fd9d2972588347d65cfc8633717b19e2c059aeae1c55f790b6ea0" - block_height: 161 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - deposit_root: "0xf125f86aaf7df54e79f43355d6be50d55daab4addaaabae4c58e7a977f3b1cc2" - deposit_count: 160 - execution_block_hash: "0xb6bbba5e6a9fd9d2972588347d65cfc8633717b19e2c059aeae1c55f790b6ea0" - execution_block_height: 161 -- deposit_data: - pubkey: "0xb249899bfe2b0b123c7a151b5041d5e994c57568a6b427e38b25f2ef04ef0c30b4577e66fa7b499ef14d8d24563ef06e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8ea309386ac878fe5d5b3e6c4dadd85302bc9432ceeb7ddaa95ec3414d7f2cccd0792606dd481c49dd4f1d3455847b9b0e07bc4726118afe68116701d4641e0ecf9caf7be829cd0dcb7651801c9bc0ad18b7592b5f95012802f1e7b14e9cdefd" - deposit_data_root: "0xe3149b68de3b804bb8ecdfaf36696dd76fcb801e7e47142e5beafd06b8c96c31" - eth1_data: - deposit_root: "0xaee25dee2d482203030ad50d3f0731d4b215bf84f1c60b875e88e19125482a75" - deposit_count: "161" - block_hash: "0x13c72c657d79ba0f34266ace9d2e195cfb91257db5853010df831bde22774d4a" - block_height: 162 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0xe3149b68de3b804bb8ecdfaf36696dd76fcb801e7e47142e5beafd06b8c96c31" - deposit_root: "0xaee25dee2d482203030ad50d3f0731d4b215bf84f1c60b875e88e19125482a75" - deposit_count: 161 - execution_block_hash: "0x13c72c657d79ba0f34266ace9d2e195cfb91257db5853010df831bde22774d4a" - execution_block_height: 162 -- deposit_data: - pubkey: "0x965ce54aa0e435602fe222441ea4ac7b227948ea37e927f9816d89d779dbeba426dc68ba6829ab8da33a565a6b879c65" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb0352350082b57df3eb8cacc48bd3e999a1b2719ef218cb2a5189291e0234f4678b21b037392c37b724ff82a6a42727b103809bea4cc34cf9c8f86675d0a40adbf65b467e9128358802e0079a0cdb100b9206a4475b97e7129272a8c36e51b9c" - deposit_data_root: "0x8b1968fceaf4e3214998a64db92e35e11a383ca34a1e8d37a4f07588debb444c" - eth1_data: - deposit_root: "0x81cbc850d30b5486ca44fc0f2368100fa3673575e87fb5635485fbd553ceb59a" - deposit_count: "162" - block_hash: "0x95088152d0c1114f167cb7b65078019b8bd5f2023e889e18c201c0bf6cca138c" - block_height: 163 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x2e6b570d265740ee405e8ebdf6c5e228efe005294f62cf1af23affc7b72a38e6" - deposit_root: "0x81cbc850d30b5486ca44fc0f2368100fa3673575e87fb5635485fbd553ceb59a" - deposit_count: 162 - execution_block_hash: "0x95088152d0c1114f167cb7b65078019b8bd5f2023e889e18c201c0bf6cca138c" - execution_block_height: 163 -- deposit_data: - pubkey: "0xaea84e54336d09257061a8b23f419438c2e3d2659de36b993033bb30e396d9c9ee8b6f1b66261a6a060c3ab706827afb" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x82804e80f3e381cec665c1f51b1015500a0eb2b35085c1b1365156e4fbb5ab82d7d4af7af12e8cfc2a73026e6aa8e3440cc9babfa8480efd255b15f1097fc8a2283d76b02f225a1d7105e44f9daffe9794208e97bbb19907dd83a0608095c127" - deposit_data_root: "0xe374e9bd5c51394fcdc6852e98614127fe095a5192cf53d340cf519bf20cfb59" - eth1_data: - deposit_root: "0xdcd46c40bcbbde1ad6887dc6099f6109980c4dd6bb67d03e7534dd7d87038867" - deposit_count: "163" - block_hash: "0xdbc232ce7aff4b99b9ae5e05a47ef313d62affb1f08328157161c00a218575b2" - block_height: 164 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x2e6b570d265740ee405e8ebdf6c5e228efe005294f62cf1af23affc7b72a38e6" - - "0xe374e9bd5c51394fcdc6852e98614127fe095a5192cf53d340cf519bf20cfb59" - deposit_root: "0xdcd46c40bcbbde1ad6887dc6099f6109980c4dd6bb67d03e7534dd7d87038867" - deposit_count: 163 - execution_block_hash: "0xdbc232ce7aff4b99b9ae5e05a47ef313d62affb1f08328157161c00a218575b2" - execution_block_height: 164 -- deposit_data: - pubkey: "0xa90dfa8114a00b3fde7cdddfb2fab9a6d113500aee32f08786634bc5c99ccd7730417e4ae4a05299b62342c1ab98ada3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x993aaf734e6ea9848eb8b581d8349a780dcd2689bd341c984ed7f291efb035c1bb6a7be47bd9904aeb1d9bc595596b670b011d2e36bf1b4a1e3f83bd87ee5d1a2b8e10d93825bdafe498155fa3b39cd1d929e858e14cae16dccac990a918b407" - deposit_data_root: "0x7b4c8cd4bdd1696c9acee551bc67972ee101a70a05d7e80e327867bf9c2cca75" - eth1_data: - deposit_root: "0xcbe12098660ef62edb564991133409b98044b1f3e79ad49c9b26a29d990694ff" - deposit_count: "164" - block_hash: "0xe2797260931e52abe1abde79d64ef6760785f83401180a0799ef7688f4118150" - block_height: 165 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x582e9a6fd990c0d996070550f2ed7e8e8cd420dd85740a5a6e53f4f0c300d47c" - deposit_root: "0xcbe12098660ef62edb564991133409b98044b1f3e79ad49c9b26a29d990694ff" - deposit_count: 164 - execution_block_hash: "0xe2797260931e52abe1abde79d64ef6760785f83401180a0799ef7688f4118150" - execution_block_height: 165 -- deposit_data: - pubkey: "0x9346419f620830d6535546fe2ddd827b69156ea9c29194780c63a8f07b6fdcb0568282914ce3cc06a2ba44e2ee1a6e9e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb243d10f9864d3abcc933b7ca0cdb2e40fa08a1631ecb702c41dab1113f0e689677ba1e8b078618989b65548103adc5d0d7531c102f22f4d502504c64148b8aded9cf314ea6d8336b5f36329e90f79298efe0d48fe6f84647dbf3952247b1f93" - deposit_data_root: "0x9843715212db9556a5ae4aebc9cd03db21467bdb82a6b0b7c8aad1e03e7c0db9" - eth1_data: - deposit_root: "0x62657a90eb6dda61c00c5b33dca2b652a03fba9f0aa4090c0711418945d8444e" - deposit_count: "165" - block_hash: "0x8090b1b61be967d73abea99aabd3a2df3ea098eee99126ff117aa1e3b0581543" - block_height: 166 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x582e9a6fd990c0d996070550f2ed7e8e8cd420dd85740a5a6e53f4f0c300d47c" - - "0x9843715212db9556a5ae4aebc9cd03db21467bdb82a6b0b7c8aad1e03e7c0db9" - deposit_root: "0x62657a90eb6dda61c00c5b33dca2b652a03fba9f0aa4090c0711418945d8444e" - deposit_count: 165 - execution_block_hash: "0x8090b1b61be967d73abea99aabd3a2df3ea098eee99126ff117aa1e3b0581543" - execution_block_height: 166 -- deposit_data: - pubkey: "0xa399755dad117a369409206196a9e3a1637a625dc22d0da18583827dbc487ab9a2ba6c995927df9d8560e07860ae8b0f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa6f0fc1f51494308f876cb2391f625009d4761871c69af305712751e35cf0f3c026e06c159e3c82988b9f97fff4d1d99078f3bd6c8b7d3d308e36eae49ac1b79ace6e04656b0fcd3d8daaf2ce6333c0ec0ca998ce3c08ea86e73fda1c8d25bbd" - deposit_data_root: "0x4e25083e3dccff55e85eaef37ccc737c720ecc898a98e8ca0c52b897431a315a" - eth1_data: - deposit_root: "0x4619beded7d11311791dcef425b8027ea14e383d8c2cd5c742879c134637ee91" - deposit_count: "166" - block_hash: "0x3c684054459e115db8518ed799e879a579a049bc9994c1727e568d3e9dbe2767" - block_height: 167 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x582e9a6fd990c0d996070550f2ed7e8e8cd420dd85740a5a6e53f4f0c300d47c" - - "0xd9c72e2b3770f12ee0e9f7d83243ab36680e0b9483eaf7a537848aae96531edd" - deposit_root: "0x4619beded7d11311791dcef425b8027ea14e383d8c2cd5c742879c134637ee91" - deposit_count: 166 - execution_block_hash: "0x3c684054459e115db8518ed799e879a579a049bc9994c1727e568d3e9dbe2767" - execution_block_height: 167 -- deposit_data: - pubkey: "0xa4695dc25f6cd20e48edd948321a09ebe2884b1d6ebf622aa02a023e96f9a1d365be7f2c6668444b347aee678fc7bc5d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x93375360b97a238600161ddf1096b86637d2bb9cf4453450804fca3b16bbc74e1aac6d9a9ece486250461b0492a68470164b8f779c6eb89f59233ec304af5cf3f76d4d5ad6508599bf569927eb97096f422051e33c4137df737eb4db9a4e421b" - deposit_data_root: "0x468fd4b82322e73a7470baa353b8931947a2bf6fe3cb62cb1b0f126973a2df1a" - eth1_data: - deposit_root: "0xb2d04fea4f590b0f56e5a7fa576d4b2cd2d88b99c09dccb2b4aa560eb0885c9b" - deposit_count: "167" - block_hash: "0x06c9e6315169cf6fbbb198a180cc67111550846dcc2b78e2265dbcb5d29b17f3" - block_height: 168 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x582e9a6fd990c0d996070550f2ed7e8e8cd420dd85740a5a6e53f4f0c300d47c" - - "0xd9c72e2b3770f12ee0e9f7d83243ab36680e0b9483eaf7a537848aae96531edd" - - "0x468fd4b82322e73a7470baa353b8931947a2bf6fe3cb62cb1b0f126973a2df1a" - deposit_root: "0xb2d04fea4f590b0f56e5a7fa576d4b2cd2d88b99c09dccb2b4aa560eb0885c9b" - deposit_count: 167 - execution_block_hash: "0x06c9e6315169cf6fbbb198a180cc67111550846dcc2b78e2265dbcb5d29b17f3" - execution_block_height: 168 -- deposit_data: - pubkey: "0xa8c0966a8c0869e28110ea5587321cb26af1bc49591fdaab24b37c77feed399439515d2aca8f2483a3d666be8b4eeef5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xace818afa05a123b98626d805d1b747f09b1d2e41c826b607da97cd28c41ca78d2514e2d4e4a0a1a98cb6034a1ace05f033e4091dfbe40eb7eb6943b682c7ed8b26b8a68f731f908ca7c18f5f8568a1c39648bb94870066275245e42e44ecd03" - deposit_data_root: "0x9dae7719d40b642b28d8de5358c902e0e7b6dcddeb88a29cc2583600c15ad2eb" - eth1_data: - deposit_root: "0x847f32ad10d7b3de3275fc8a8730008ec5f12a89142209a4ba63c8cbdb022ea3" - deposit_count: "168" - block_hash: "0x619b700d81b9b1216b6b9ff1f0eb4f7b11c4ffd3c4cf781af2a9cd47ef42a7e5" - block_height: 169 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - deposit_root: "0x847f32ad10d7b3de3275fc8a8730008ec5f12a89142209a4ba63c8cbdb022ea3" - deposit_count: 168 - execution_block_hash: "0x619b700d81b9b1216b6b9ff1f0eb4f7b11c4ffd3c4cf781af2a9cd47ef42a7e5" - execution_block_height: 169 -- deposit_data: - pubkey: "0xb210d799f2cc4d87df36b60fe1d7408d9e4b5124aeed740eb42227dc1f456d9e13bcecf309e068f9775996c71b55ca8d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa59636627fd11a272aac90b9ae21cbe3156c8e347f34c6a92d6078f1a130864026c4fd8149df5c0808c9d8be6e7257960c627011fa459fed2d513ac91bc10ef3858e27ea6b88aa3d1f566ada6075daf2f3306fed983353dc6593375f9a2af410" - deposit_data_root: "0xd2ff2f73a25e4c4de82d2bc2f1fe937bdaecc03fddfb5f971df8c65a6fd9d93f" - eth1_data: - deposit_root: "0x7e03a31121d9081c87abc7e9789a6ba49abf30fa5e5b0e2245cd75c76869ad5a" - deposit_count: "169" - block_hash: "0x307cdbb86ebbce74ab00d13e63aaf58c789c1fe21b037234c2798cf2e44ac41b" - block_height: 170 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - - "0xd2ff2f73a25e4c4de82d2bc2f1fe937bdaecc03fddfb5f971df8c65a6fd9d93f" - deposit_root: "0x7e03a31121d9081c87abc7e9789a6ba49abf30fa5e5b0e2245cd75c76869ad5a" - deposit_count: 169 - execution_block_hash: "0x307cdbb86ebbce74ab00d13e63aaf58c789c1fe21b037234c2798cf2e44ac41b" - execution_block_height: 170 -- deposit_data: - pubkey: "0x83af2f04a869d856df934893edd7f15dae94ef74d78139e0556e1166e81f8ab2c294708af67f194e854946f2da4e87da" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x944859b17259fedb50e94bf3fbee57b0d5c7f43401fb611258a599696f24d94622a71d179d540fb83b475122c406c85113f84ba8684096c38accf6d171ae2abefb725a638207dd12d12099a7bdeddb832a05f4707797d724deea9570679dab0e" - deposit_data_root: "0x9aad59fe2860067c1859e09c5a0cbd41a482389f2457b3ddf246b065d2b797e5" - eth1_data: - deposit_root: "0x0507a6d86c34e8fb6f2e644d93bea484d395eb00f747aa4185e15e67741708f8" - deposit_count: "170" - block_hash: "0x4840cbf1750adce5ef26a3b6e0251a60f5998a1bb4109a3918410e2f459edd2c" - block_height: 171 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - - "0x0243f87cc01998a622e06a46d90ff0e2c2f4e0457b4e0c418a13a3183743df4e" - deposit_root: "0x0507a6d86c34e8fb6f2e644d93bea484d395eb00f747aa4185e15e67741708f8" - deposit_count: 170 - execution_block_hash: "0x4840cbf1750adce5ef26a3b6e0251a60f5998a1bb4109a3918410e2f459edd2c" - execution_block_height: 171 -- deposit_data: - pubkey: "0xad69af5d2ee0d68b32e6c4ebafc348a0c509aeed7e4f5c24c236dad4a8f91129cb9f8ee521de07c8199807e36e6a84f9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8a8c6e627da5d712418638f0a31f9671f753d44f5b9439ba0ebacd3a4cfbe41b6ec1c75eb70e0e57bc3535409e7f949f01dbafa227b0b87d44ebc5dd63083e3848b36f4b1821f0f3c1ff2a9878c5ba2cdcc16e39b01e0f4a924aa8c14278c447" - deposit_data_root: "0x41deea4572826ab6dba52032eeabd9996d80e3a5725ebba8a8a20726fd16d10a" - eth1_data: - deposit_root: "0x73170edf9923bea7b1203c4701f3023cb42ab5a8c15e7a5310fdae89f49a10e8" - deposit_count: "171" - block_hash: "0x409571a87e6260ebf8a4b04e70a01d1276eb29c3ea26ec4f85561f1b15089ea6" - block_height: 172 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - - "0x0243f87cc01998a622e06a46d90ff0e2c2f4e0457b4e0c418a13a3183743df4e" - - "0x41deea4572826ab6dba52032eeabd9996d80e3a5725ebba8a8a20726fd16d10a" - deposit_root: "0x73170edf9923bea7b1203c4701f3023cb42ab5a8c15e7a5310fdae89f49a10e8" - deposit_count: 171 - execution_block_hash: "0x409571a87e6260ebf8a4b04e70a01d1276eb29c3ea26ec4f85561f1b15089ea6" - execution_block_height: 172 -- deposit_data: - pubkey: "0x93568c9c40bb362329367dfc26a65567481a03c35fcaab51f781c9f364b7e676cfc3d2c08633888b29715a6731dfb6b0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8afaca7a98ed1bbe7d5ad827d9dfb25e22983515a8cb7bbf9660b8bc965dbd22cddcf301be0b8ea07773716cd6bb40c0939ee4236b1048b327b51dc56eeec3333296519c33ba8b42671c2e64941d3c0274204eff17c255729a6ff036dca11d2" - deposit_data_root: "0x3ce86437d73f789ed89cc006c250d5ae9ebe44e25ce3df3b8a87c511f3252ab3" - eth1_data: - deposit_root: "0x5f0c1f7e6a02232a6a1d7aa1c7a94f9d46700a5715138165282542665559fe6a" - deposit_count: "172" - block_hash: "0x4f2e4afd489e40fdb8ecfe276f4a028e550ae9f2224c29aff14670bff6732ac1" - block_height: 173 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - - "0x6706f41e298f5bf853c88ba459952003db6fa9dd7a4c36b72fdca4b7d831d42f" - deposit_root: "0x5f0c1f7e6a02232a6a1d7aa1c7a94f9d46700a5715138165282542665559fe6a" - deposit_count: 172 - execution_block_hash: "0x4f2e4afd489e40fdb8ecfe276f4a028e550ae9f2224c29aff14670bff6732ac1" - execution_block_height: 173 -- deposit_data: - pubkey: "0x8509086b192c039cc84d145fd6a2b2cc3e3a3d46092e928cc1ca9a66dd663550a2782ab32d677785e02c23b6637df70d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9055741c269436e5cb5eb9560441a9dd62dc3605913b6b83b207e49eb2c9f3837b2c877c8e937ce326ddfda973a031920f60331a5eaadc52555cf37c6a0216ca8ac3ba965a2c0060b1b9729472e977a5fd23b23c0a39286b849a7ee22d76fe5e" - deposit_data_root: "0xc9ca71afaa9631f5c9645212720807c19c302df3a5911504c49fc87f78011862" - eth1_data: - deposit_root: "0xda5ea898120a262741057304c7596b6a34af9ca93abc2a162c738cbf30613c57" - deposit_count: "173" - block_hash: "0xd53d21964f2af07692e1fa7c1886d583dd28693ea39b391d4ebb18e603d0ecb2" - block_height: 174 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - - "0x6706f41e298f5bf853c88ba459952003db6fa9dd7a4c36b72fdca4b7d831d42f" - - "0xc9ca71afaa9631f5c9645212720807c19c302df3a5911504c49fc87f78011862" - deposit_root: "0xda5ea898120a262741057304c7596b6a34af9ca93abc2a162c738cbf30613c57" - deposit_count: 173 - execution_block_hash: "0xd53d21964f2af07692e1fa7c1886d583dd28693ea39b391d4ebb18e603d0ecb2" - execution_block_height: 174 -- deposit_data: - pubkey: "0x89494e98d8cc4c763b3b138e21d6cc1c86f7eeb69315cf6a4b8b5b7018dd59e3b82c0b7c785a381ffd1b809e5bbf4625" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa937d3c9c04bf31b388af13d98636e1339e5e9cf3d8dfe50dff7722be110fb42e68efd406af92b5e3c0ed4f73959c89502c84a8ef20410120d5344b7441872686652f642ccc7b2d944e3a6937670e166795535e65fc66204fbc61a9e846a9950" - deposit_data_root: "0x000a7e1720ce430ce6344f7add0f7e4b43ca5ca57be2a8efbf63c0746787cea2" - eth1_data: - deposit_root: "0xb349cf11fad3f0efcc1b018ea084cc30ff6ce11e8c13ff066a548ca67bb03906" - deposit_count: "174" - block_hash: "0x446efd92f90c3861d7acb8deccea5b7c28036a54dd441584508a5c8a52f905c2" - block_height: 175 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - - "0x6706f41e298f5bf853c88ba459952003db6fa9dd7a4c36b72fdca4b7d831d42f" - - "0xdc8f10386657c08bb0d17aba01f7647252ad4c6db5a36fe938283fe819b0c9d0" - deposit_root: "0xb349cf11fad3f0efcc1b018ea084cc30ff6ce11e8c13ff066a548ca67bb03906" - deposit_count: 174 - execution_block_hash: "0x446efd92f90c3861d7acb8deccea5b7c28036a54dd441584508a5c8a52f905c2" - execution_block_height: 175 -- deposit_data: - pubkey: "0x8451860d32c95e30f685cf31fccb967b0cd172566ff7b7d2c5ff35130bd1232cb1a216c9f0cd355ad69a93469b78e8ba" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaaeaad75e6abb6123819f1f01b636a91f85f28c38fe57833e38e555f0a78546a151c02dad44fce569eb89212879c381c18a516d9256ca79afde66a5fc334005b470dd2e3d704248cd7f61203ede9fffe343dfbd0b55f0a24b1c832be7ae68cd6" - deposit_data_root: "0x38d3cf596f6aff5b5603a02e0cab7770a129d72499ec88ae2cf89bf01ba15b8a" - eth1_data: - deposit_root: "0xa927a1cd6a0fd5bfcbdc12921e8197df198e82b0fd4ec52051885184f7105703" - deposit_count: "175" - block_hash: "0xbce83de010f32cd9de9e1af87710aa65227fb59b04ae6825ffe42bbca51ad8fe" - block_height: 176 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x51ed246dd0a98ad17d95eb60d315ce5433d7a022cd5db0a6d4f1be5b3bbae2c1" - - "0x6706f41e298f5bf853c88ba459952003db6fa9dd7a4c36b72fdca4b7d831d42f" - - "0xdc8f10386657c08bb0d17aba01f7647252ad4c6db5a36fe938283fe819b0c9d0" - - "0x38d3cf596f6aff5b5603a02e0cab7770a129d72499ec88ae2cf89bf01ba15b8a" - deposit_root: "0xa927a1cd6a0fd5bfcbdc12921e8197df198e82b0fd4ec52051885184f7105703" - deposit_count: 175 - execution_block_hash: "0xbce83de010f32cd9de9e1af87710aa65227fb59b04ae6825ffe42bbca51ad8fe" - execution_block_height: 176 -- deposit_data: - pubkey: "0xb48c495c19082d892f38227bced89f7199f4e9b642bf94c7f2f1ccf29c0e6a6f54d653002513aa7cd3b56c88368797ec" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xab221d4174ca55a27aa072b675ee700d63edd048d4f417b79bce310c3bc3f5aa214eca529c4624accb014bad5d1f79e50ea67c82e4b3daea65dda4de263c0131eff1ab811e5716eca36bc2409495ae2795abbeb161311df4a3289cf1aff624e8" - deposit_data_root: "0x960ceb47ae45f726f760f1a6e4c501a1e539f332f3cfad2dcd22be596fc8c126" - eth1_data: - deposit_root: "0xe95f5528ca45a8860a620ea21eda926f6f153cbaaa6b9324ae5ea82aff8dc2b3" - deposit_count: "176" - block_hash: "0x3f7e984a3ba436e0ed8fbfdb1bca8308e6ec5b1060400a1c5156af6ef110bc5d" - block_height: 177 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - deposit_root: "0xe95f5528ca45a8860a620ea21eda926f6f153cbaaa6b9324ae5ea82aff8dc2b3" - deposit_count: 176 - execution_block_hash: "0x3f7e984a3ba436e0ed8fbfdb1bca8308e6ec5b1060400a1c5156af6ef110bc5d" - execution_block_height: 177 -- deposit_data: - pubkey: "0x8bd22839c85ec58af4303cde58674394247fbe1f51b4e30ebcdf86aa861ebef2ac9239aa21bd9b07fd03f28dc4806780" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x81fb9baf8c52a4888566448ed653d4e199b40bbf4053c34ea0105554f96a4dd245df81a2ed37e900b73ac954c7001f7009a2240737f034d9e4ca6874f3913d06efbfc2eea789f86ad5a353b37b755ba73aae5dcb5f3f44fb155410807d44bbc0" - deposit_data_root: "0xfe4956baff4bc251951ba1c955db9f46821ab844d6a031ef7db7b68de6959856" - eth1_data: - deposit_root: "0x76342485c471517c60dce8cf762fffbf3f06f93202e98682c7078618c4eb0e88" - deposit_count: "177" - block_hash: "0x66c1f5247e10372a47af81e6e30ffdccc3f0d64f7fde478420394289c435dec7" - block_height: 178 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xfe4956baff4bc251951ba1c955db9f46821ab844d6a031ef7db7b68de6959856" - deposit_root: "0x76342485c471517c60dce8cf762fffbf3f06f93202e98682c7078618c4eb0e88" - deposit_count: 177 - execution_block_hash: "0x66c1f5247e10372a47af81e6e30ffdccc3f0d64f7fde478420394289c435dec7" - execution_block_height: 178 -- deposit_data: - pubkey: "0xaace874118a4ea9cfec8d7979dbb4618f4833dde4cfc493a403ee5cedb67f294bcac4aaa2d626ff25f4fc7ee9fa61401" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x859e833217c0fd90fc641745e75e0d100d6b3cdfc1fc98dcd92dd0838a22c3bb3508ffbfc41662ec93134881f4a75a7d01661c1eb91d302c8ad28463703fa445276f9a7ce810a0fe475e18631ba1b81084f2b0c65008b37eccffbc01eca98143" - deposit_data_root: "0x2325c1ee7ee609107dc5df1c7445e7d02a00e48798fe06f73e48705e0357e8b4" - eth1_data: - deposit_root: "0x95111c86ff2b172022650c8d000331c7c57d98b2f330aa9bd6bd417f94547739" - deposit_count: "178" - block_hash: "0x0c95d8c50c7fa3299c3ffd7ac683ef683fc907c39997b8b606bc8210c71c1b9b" - block_height: 179 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0x452e7913b4f3ad6bc209cce6fc61588821ad6494282520a51e00926d913aac94" - deposit_root: "0x95111c86ff2b172022650c8d000331c7c57d98b2f330aa9bd6bd417f94547739" - deposit_count: 178 - execution_block_hash: "0x0c95d8c50c7fa3299c3ffd7ac683ef683fc907c39997b8b606bc8210c71c1b9b" - execution_block_height: 179 -- deposit_data: - pubkey: "0x956851460cb809871966cc4dd44d0b58dc68a1c22110864f374cd3aca743ee63b0743999d35b473e3f95ea38a276eaea" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa22edc0fab4e7099060fb062f9ac88bc08e2903dba9c9243dae581b169e91a3a8a823fe5a429d0d459d6499fd70c1c240d33cd4f5d7053ba9278234fbced8df69ed82849f3fd8510658918a00720c80d8453044bad1c4bdbdb20ca5ff75ffa5f" - deposit_data_root: "0x9567c1a847009105673244cbcccff11184117f0b3e465f48e1771ead7976779b" - eth1_data: - deposit_root: "0x358b5a17f9a64b0a891cf6741ceebd0b615dfb89490550c9b601551d13781068" - deposit_count: "179" - block_hash: "0xd6917e5c5a35c21ea97cb5bd1f9070427106bf4980bb247d8a25fed86a6d09cb" - block_height: 180 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0x452e7913b4f3ad6bc209cce6fc61588821ad6494282520a51e00926d913aac94" - - "0x9567c1a847009105673244cbcccff11184117f0b3e465f48e1771ead7976779b" - deposit_root: "0x358b5a17f9a64b0a891cf6741ceebd0b615dfb89490550c9b601551d13781068" - deposit_count: 179 - execution_block_hash: "0xd6917e5c5a35c21ea97cb5bd1f9070427106bf4980bb247d8a25fed86a6d09cb" - execution_block_height: 180 -- deposit_data: - pubkey: "0xb8cd27d87c94a69cecd953999908640b437f6215ddae069a1ad403f995dfde6e4ac46e5c85fdd8bd4fa655f74cc2bc80" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x986d671909e02d5efe0689ec87b8add58331b963e14b4ed9d989b586f1bbd1c5f119360e1bff27fe64e598601545c52d08386c175a1fff8acf5f6760d12acfb51027523ae9e7afd69ff4db75a1e580904ecf93a22da7ef83c5cbf08f92bc85cd" - deposit_data_root: "0x9c0182b4b9d2a650eee485c0a5b6c743d4377dfb2dad1094972ecb50271f65a6" - eth1_data: - deposit_root: "0xfa89ade12d8c6d2a1d9800b7bb254622b424c40067e2b0b9e569bab078c024e6" - deposit_count: "180" - block_hash: "0x6c27c6e514081d3f51296640528a1da12d22a3462cb44810dad190c782f7cbde" - block_height: 181 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0x2d162bc4799fcfca576dc1ddf85c96c62bf3506922863de03d99b21d5d57b1fc" - deposit_root: "0xfa89ade12d8c6d2a1d9800b7bb254622b424c40067e2b0b9e569bab078c024e6" - deposit_count: 180 - execution_block_hash: "0x6c27c6e514081d3f51296640528a1da12d22a3462cb44810dad190c782f7cbde" - execution_block_height: 181 -- deposit_data: - pubkey: "0xae37d415dda04db4f1abcf52c080c1c7a1921a819181161c4c2c18f809cdcf695de3f0f793c78802bacc1f4a32bd921a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x97d0c9476aeb800a3e27f0b60f371679789e6bb03919bd65503eea6ee138275da5ea35f8a21352d68f1e5cde6be870d50c5356047cca0a2f043d83acfe44d3732e19f2aa54e844ccc57bb72b2061b00100cd28593eeab5601f35a594a2e82a98" - deposit_data_root: "0x0da07a675920dd21e72440e13ac21d1d8d9864c92db43ffbb3bb100d6d112923" - eth1_data: - deposit_root: "0x46cb3dd3672791d77dadce6782dff84a88f34d594a9dd8441473f26bbff0e3fb" - deposit_count: "181" - block_hash: "0x7e23db8c0087f9044534d05457249a8f3bf7d50496eb005325709a44bc102b32" - block_height: 182 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0x2d162bc4799fcfca576dc1ddf85c96c62bf3506922863de03d99b21d5d57b1fc" - - "0x0da07a675920dd21e72440e13ac21d1d8d9864c92db43ffbb3bb100d6d112923" - deposit_root: "0x46cb3dd3672791d77dadce6782dff84a88f34d594a9dd8441473f26bbff0e3fb" - deposit_count: 181 - execution_block_hash: "0x7e23db8c0087f9044534d05457249a8f3bf7d50496eb005325709a44bc102b32" - execution_block_height: 182 -- deposit_data: - pubkey: "0xa7c674e6660a1930c546b4a7e345265a51527fbd53327f90cf7edce01ec2a9c9470c9d9f0a2e4dcfe4c0c5df4382aafa" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9765a98cfa6c562e97d0d18e26d20649229ecc001700852fcebdc804c14ad8fd6a9e4169972e16c0abcc07912469267d0f62d285abb659530eaac69c14dbace530e4f3d37b89ff58242c534c1e87a993dd0e53e52151f0021a699827196ab3fb" - deposit_data_root: "0x3c02d11665be2b3121a49de3fc627ae4d2728050be2857acab0ab6800d30702f" - eth1_data: - deposit_root: "0xc2bd6a75ddac26bebb1dedff17e01e7388ba0d1daacad735c06dadf4d1497ab8" - deposit_count: "182" - block_hash: "0x24b5eec3f6b3ca56fe7e893edabe438ac14e338fae43ac6ad28b231a79e66768" - block_height: 183 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0x2d162bc4799fcfca576dc1ddf85c96c62bf3506922863de03d99b21d5d57b1fc" - - "0x3a0f8fa7a03d6c1c8c53c26f944126d830c08b1cea4019c7980dc8d3cca5aeaf" - deposit_root: "0xc2bd6a75ddac26bebb1dedff17e01e7388ba0d1daacad735c06dadf4d1497ab8" - deposit_count: 182 - execution_block_hash: "0x24b5eec3f6b3ca56fe7e893edabe438ac14e338fae43ac6ad28b231a79e66768" - execution_block_height: 183 -- deposit_data: - pubkey: "0xb2c9669a6f3e64a5f8b37c210c88adb507ce396042921b1aefd5bf52a3089f0ab0b5695cddf9b1a1157fe5dd43558e54" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8602ae96ab5650a7e477de5f00e10cb53826e3a268d96dbc0bd925e7370ff3f3fa7258261eb75a58d74c4de64db6713d19293e04d0cab40c1bd0c4669a7dd87a2b6272b8dd3c8f6414290843a4fbb945638eaf09594d44f502e2da1229d0ec3b" - deposit_data_root: "0x68d5efabced0e3a67465028e2e9786d3626da8f47164112799f6280044e1da6d" - eth1_data: - deposit_root: "0x0e02f967291a37a27493d09d2cd5b530d83b67accb29fa7378acb5294b878af2" - deposit_count: "183" - block_hash: "0x392303f20a0d6202e844fffb6ac069f6a9dbaaa3f4447a7997afc74f1a83ee13" - block_height: 184 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0x2d162bc4799fcfca576dc1ddf85c96c62bf3506922863de03d99b21d5d57b1fc" - - "0x3a0f8fa7a03d6c1c8c53c26f944126d830c08b1cea4019c7980dc8d3cca5aeaf" - - "0x68d5efabced0e3a67465028e2e9786d3626da8f47164112799f6280044e1da6d" - deposit_root: "0x0e02f967291a37a27493d09d2cd5b530d83b67accb29fa7378acb5294b878af2" - deposit_count: 183 - execution_block_hash: "0x392303f20a0d6202e844fffb6ac069f6a9dbaaa3f4447a7997afc74f1a83ee13" - execution_block_height: 184 -- deposit_data: - pubkey: "0xb00c6d2cf271b167f17135bf7bd14b7df669194045eb6f09b9dec787c7b608dcd42613dab0f857fbfa3fb84792a3e63e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x81bf33d08664f53464ad3fffff9475069948094c54b61edba63fb435969b7e621f7349991d72b43c8dd1bc48f20ccf1800e20a812f9996e115e9a98c98024186c4ba49722dbe463ea6c1c897105f24f5f983b2bf911c3af59123e43edfffefc0" - deposit_data_root: "0xe2a3b3697c4174b669c5c9c3ce72ee244719fad0e9f198234cc3a13b7afcf5ef" - eth1_data: - deposit_root: "0x35db931e35f47dd387ae57bba681831b91a6579f034a754e7df92a8616679861" - deposit_count: "184" - block_hash: "0xa3d47fe70c8d2099b59a84c9f42bbc91c5bffe370010d5e535657fa3a1c4c816" - block_height: 185 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - deposit_root: "0x35db931e35f47dd387ae57bba681831b91a6579f034a754e7df92a8616679861" - deposit_count: 184 - execution_block_hash: "0xa3d47fe70c8d2099b59a84c9f42bbc91c5bffe370010d5e535657fa3a1c4c816" - execution_block_height: 185 -- deposit_data: - pubkey: "0xa5e5e5af2ef8b65bf9b7575606f81e99d1fa645078544b0fcd4e0b506670e6a75504e6ba4001e6cec25632633d050276" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb05a3fcac399a997e7ada5181638bdd95668dbef59f2a62b9c45a85857a42afa5f885748f6d07ecdefcae45e736ec3b512a94bccd7be7bbaec688d9449f586ffb48a82f3c693d1248932d7b7026082ccd7c0a97aae7e714f39484614690abd72" - deposit_data_root: "0x2c3e00b8923d7c7bba4859372c106c9aa052b00d19a1cd9fa3aedab4a5511741" - eth1_data: - deposit_root: "0x87e828349d121957dab0e13448ba28216514999e6dfff614978f171578e27e2b" - deposit_count: "185" - block_hash: "0xc2320a2fc8b6dd2243c3abcdde3847e2598ffb7aaf303ce46cb2416d8e3be4cb" - block_height: 186 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - - "0x2c3e00b8923d7c7bba4859372c106c9aa052b00d19a1cd9fa3aedab4a5511741" - deposit_root: "0x87e828349d121957dab0e13448ba28216514999e6dfff614978f171578e27e2b" - deposit_count: 185 - execution_block_hash: "0xc2320a2fc8b6dd2243c3abcdde3847e2598ffb7aaf303ce46cb2416d8e3be4cb" - execution_block_height: 186 -- deposit_data: - pubkey: "0xb7ad21b3cc61c96c4ea90e81cb5a39836f114dc477bb63da54af20f60d42dffb99aac4ae12ecb70288b1d226ca7c2522" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x80714ff567371483af7418f8d43a7e4adfd10d8d464e3fad6f72ca3d981920aa58d71235f1ffc6296b89a8744b66b23519367b3f5691e4410ca3bd068d28d88b4fedfdd91ac4ebbfe5a176827576c3a7176d4bb04f28c854051354db197d4112" - deposit_data_root: "0x56f3cf8c6f87ca6f6298746c6aebf716e8d2387075aa400a69746cc5dbc31815" - eth1_data: - deposit_root: "0x7ac99b76e5fc809c4327f6edf9142d7bc55e12c3f9089a53066ec41500dfb5a0" - deposit_count: "186" - block_hash: "0x92708be0274d0ac8798c19482761e0a59edeb96edaf6e5fc2c1588a3365394b0" - block_height: 187 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - - "0x9856919205a162db29485432bba026c979f7d89cfce702fc09657632a7a4f14a" - deposit_root: "0x7ac99b76e5fc809c4327f6edf9142d7bc55e12c3f9089a53066ec41500dfb5a0" - deposit_count: 186 - execution_block_hash: "0x92708be0274d0ac8798c19482761e0a59edeb96edaf6e5fc2c1588a3365394b0" - execution_block_height: 187 -- deposit_data: - pubkey: "0xb3eea1ef03b3cd28709513f691d9b7aefcb9908efc7114e20e302598cc8dfcea02fd9423045f246112e4db1bdcfa9e14" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9576775970091bad8e182acfa971d69815b901f7818738490d7a94bf3b7fd5de9810aface9670ad6d4074295cd91c3fe073a989aaaf4143cdb6da3a0a6545e749cdbb914b38c9d3dddbcc5b57552ac5711f91fa2c59fe7ee52fca41c248dd7d4" - deposit_data_root: "0x0fcd143f7e8ffc456987f9c887bb46743be683a31a84fbbfead37a6929f1d0be" - eth1_data: - deposit_root: "0x4a796a1cd10ad42e890c811c5261325c80df56afcfdb45b36ca98eca59d1cc1d" - deposit_count: "187" - block_hash: "0x8f679f6f3487c47c32be42dc94ca7d0bdad809ac30d9c8c38ad5010c046af1e1" - block_height: 188 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - - "0x9856919205a162db29485432bba026c979f7d89cfce702fc09657632a7a4f14a" - - "0x0fcd143f7e8ffc456987f9c887bb46743be683a31a84fbbfead37a6929f1d0be" - deposit_root: "0x4a796a1cd10ad42e890c811c5261325c80df56afcfdb45b36ca98eca59d1cc1d" - deposit_count: 187 - execution_block_hash: "0x8f679f6f3487c47c32be42dc94ca7d0bdad809ac30d9c8c38ad5010c046af1e1" - execution_block_height: 188 -- deposit_data: - pubkey: "0xb0f147fdb3e17379f4933c18e6dd3e3c9e9414ed5920ab029dc5907c01f671f664e4bfde8a4bcfa273a57daeb824f695" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xadcea3abcac1ad298bc1711dd9555c2f9802e83e406debfc65ba54af21f99d652f440ebcf584d55915905dc5608c8e8a03081a51dd172d8cb0d11739ed1d6ffc7e9c0100cc4c565cdf7ca8d287f121dc7892583490629aaa93d2da2b8cc57d8e" - deposit_data_root: "0x35cb7c2582792d09b08d2b5da3edb333de2b9074576999f0bbb0891adf6fb9b1" - eth1_data: - deposit_root: "0xab52ed24e0e69808c8d21fb5ca3ad3ec64c02344b953dd830261b97e3d7356d5" - deposit_count: "188" - block_hash: "0xe762ae9acb10f13c7f8e344c9fef7ee760577fe56c516050e1921cfa41e339c1" - block_height: 189 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - - "0x1ad4ff6de258418fe600f0c8dabec3d6665177e5929c3570071111e85c21a90c" - deposit_root: "0xab52ed24e0e69808c8d21fb5ca3ad3ec64c02344b953dd830261b97e3d7356d5" - deposit_count: 188 - execution_block_hash: "0xe762ae9acb10f13c7f8e344c9fef7ee760577fe56c516050e1921cfa41e339c1" - execution_block_height: 189 -- deposit_data: - pubkey: "0xae04fca0e06b256e03d4173d7f772e2106efe6f72d469243dba695179b5d2be8b61052c2844247ce49ec3b6cfd18c73d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb32e41533c45bff774de491d8b06cf576a27225dcd8b7713e980a7f61eb6bd4e53ba34484c0e061e38448e4bd4859868172fd3f4da5ebedcc4cb322f675ace60d7272cef4e7c27afaf8aa6f46d2b575bc14208ce19c6d1a29e759b883d70f590" - deposit_data_root: "0x9426eb1a7383fe76d2759e9482e7cb6aca0b0b7afe88bde72a55c4a1c54d56b1" - eth1_data: - deposit_root: "0xe4ac94ad06eaff8884701227e53fc6cc79a3471f92bd7891f31432c1ad4b1e6d" - deposit_count: "189" - block_hash: "0x167dbceea97680bfd280469520320efcef8881d4f735bbee384fd13f0b8b6d48" - block_height: 190 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - - "0x1ad4ff6de258418fe600f0c8dabec3d6665177e5929c3570071111e85c21a90c" - - "0x9426eb1a7383fe76d2759e9482e7cb6aca0b0b7afe88bde72a55c4a1c54d56b1" - deposit_root: "0xe4ac94ad06eaff8884701227e53fc6cc79a3471f92bd7891f31432c1ad4b1e6d" - deposit_count: 189 - execution_block_hash: "0x167dbceea97680bfd280469520320efcef8881d4f735bbee384fd13f0b8b6d48" - execution_block_height: 190 -- deposit_data: - pubkey: "0x98c4e0f20616fa174bb221011e731650cb841ec29305f074fdafbed1d04b761c107a854722d4a6d69d6a15f7bf85c7d0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x974f103ca1b3e597a8edb9ddc0a26388902a3d016d25322bbbd3f8160117472cf6a8642f9ba00b40a364463e2598226607ddd490c88f48878dfe288bfd1eafeb1216d7702b80180afce9c3c891fb566795a6cf7a29ebabbcd729e735ae999c15" - deposit_data_root: "0xa35b4a77af8ce0b6db5f6d71cfea7feae8cb334d1d8bf25b954c9c5e46ca812b" - eth1_data: - deposit_root: "0x3f7a7f8f15b24ce7b68da0583b9a3ea05e4eb68a97db64aecaf1be63dda51842" - deposit_count: "190" - block_hash: "0xc6adc26aa6328f1ba0efacfa64978e016f0cb79732f8d0030e5c5a908cde788f" - block_height: 191 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - - "0x1ad4ff6de258418fe600f0c8dabec3d6665177e5929c3570071111e85c21a90c" - - "0xd3cdd6fba6102cf506ca28a01aff74996a181dadaa9bf9819ff0fbffa9461e53" - deposit_root: "0x3f7a7f8f15b24ce7b68da0583b9a3ea05e4eb68a97db64aecaf1be63dda51842" - deposit_count: 190 - execution_block_hash: "0xc6adc26aa6328f1ba0efacfa64978e016f0cb79732f8d0030e5c5a908cde788f" - execution_block_height: 191 -- deposit_data: - pubkey: "0x8800aa633b5ab60ea1da84e04e4a2fe6fb19c1d90197791ae6212e5349d306ff58afe02d4dfad739b07171ab5757d26b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x98f03706cdb826e0909662f03081bd021f6c9e45eee0ca23ee0d99af23b1aee1b89f7f416a54ec69060f8ae12e28da4411cb16e3395d670c2ec8391e334a7de5691c5b2580f60929ec0c6b3cbefd4c98af66fbe609461338aced45e3e78b7c6a" - deposit_data_root: "0xe0b69fa6a6be23e2f39b421d4f53ca95608f755be6f1e6f3cd131e4845a9557e" - eth1_data: - deposit_root: "0x3f9f9fcb0d2dddda55497b58cadb8ea93f1f3b741656a4849bb7fb0fa6722521" - deposit_count: "191" - block_hash: "0xc5f1e8d399f145ef92628f2d687619db3d5981fbf51dae54ff28b1f6641dce22" - block_height: 192 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0x01a4a7e7ce6c883eaa125ca6fd50f1342b9d35c305634bf843d7a609cc8b36f8" - - "0x3bdcdb12a251d536acdb1af1c54acb37cd882c8fefa579587efd7828584f5422" - - "0xc81cb8f54677d5a3e4f8f079429f97cf7ea6fb35b82afc3d691763085cb8c78e" - - "0x1ad4ff6de258418fe600f0c8dabec3d6665177e5929c3570071111e85c21a90c" - - "0xd3cdd6fba6102cf506ca28a01aff74996a181dadaa9bf9819ff0fbffa9461e53" - - "0xe0b69fa6a6be23e2f39b421d4f53ca95608f755be6f1e6f3cd131e4845a9557e" - deposit_root: "0x3f9f9fcb0d2dddda55497b58cadb8ea93f1f3b741656a4849bb7fb0fa6722521" - deposit_count: 191 - execution_block_hash: "0xc5f1e8d399f145ef92628f2d687619db3d5981fbf51dae54ff28b1f6641dce22" - execution_block_height: 192 -- deposit_data: - pubkey: "0x9474c26852bc2adfc52d093351dbf7ea822a2639d99db5b0d344e440e0ebbd1e856ae37daf3d91a05cf62f33342bd790" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x906782c06b770529831187f04df19f8c727112b36499e90e0ad918cfb44318fc0c3eb8e76184c0b5e40b088d93ff645a1903c216805754913fccff7f99c705fa735ef28f0343a7fc0f416bff0855368ab5d168e5b1a4002d149dddf9a7fbfe6c" - deposit_data_root: "0x36a01af91aeb0f7858b003955815f552bdec183db519e9c8210c18560fdb8d1b" - eth1_data: - deposit_root: "0xa0fb785cecda8d27d5ce055f5cab99945fd8fb66281532c8d8b51e7596117bc0" - deposit_count: "192" - block_hash: "0x69d947eb6e6297a9449d82c807920062fc057f18e168cc0bc06bf5a89a8aa275" - block_height: 193 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - deposit_root: "0xa0fb785cecda8d27d5ce055f5cab99945fd8fb66281532c8d8b51e7596117bc0" - deposit_count: 192 - execution_block_hash: "0x69d947eb6e6297a9449d82c807920062fc057f18e168cc0bc06bf5a89a8aa275" - execution_block_height: 193 -- deposit_data: - pubkey: "0x86c82451f5ea5981c9031b3871e1463b10875934eda1c2520752d03bd51e71d943037b1a902979b6821eee7bd5779f78" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa9fe9f4af4705bda5e0ce2a369ebeef5ee8cde8cd9a66303e0f8100f282b23a5441a9c5be3c4ba6462d28117626fbdb6157951056078532b6e1717b5d5e44fc0839cefc1e34983900027caddcbffa2404014a35b16a3f6adf6cdcf35d8cdb396" - deposit_data_root: "0x165cd78151e962dad0f4d9de56e66436d07d08beca179d0bf92a9b534791aab8" - eth1_data: - deposit_root: "0xc9fe80c8ecada066e8d0fdb2edec4b23b5f6d116bd65b980a9000c9fc251031e" - deposit_count: "193" - block_hash: "0xe2aade60b0fbbbad2f998e5badd1b8d3ced7fc0f7ec6f52ad7be45be91f38ec7" - block_height: 194 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x165cd78151e962dad0f4d9de56e66436d07d08beca179d0bf92a9b534791aab8" - deposit_root: "0xc9fe80c8ecada066e8d0fdb2edec4b23b5f6d116bd65b980a9000c9fc251031e" - deposit_count: 193 - execution_block_hash: "0xe2aade60b0fbbbad2f998e5badd1b8d3ced7fc0f7ec6f52ad7be45be91f38ec7" - execution_block_height: 194 -- deposit_data: - pubkey: "0x864a08f5f022ce7757cb73be7ebf54e387fb3d5d4b0371d01ed493270be3236c5bc3b1272e42d2628f8f3c000d60eeaf" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x91b20dfe5a0f41fc5e8cb0b591faeed60ccf42c7d7ead8c17ba7075c6349bef80a3f263f175a57b6b3a4a1fc2ebb1a800d2f1c4f5d45713d1ee418fd903c01c168c507a01c207d182cfe012342bc0f57707a24c7c0df942e247540bb97fd824d" - deposit_data_root: "0xdc1915b04568269a1f5dc65e91d8b380c104621822d27c6e4233a878ae0fef5e" - eth1_data: - deposit_root: "0x5f5a8158ce7e1a776c1b6187d70abb5f6676b608626c8d051abe7795c8435946" - deposit_count: "194" - block_hash: "0x32c72593ddc2739d5eae88440cee25dd36d46751816d1e6f8eed0ef30de97fcf" - block_height: 195 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x735a400964fac7b2378423de5960b50eaa02589ac565a271de0129a009f66614" - deposit_root: "0x5f5a8158ce7e1a776c1b6187d70abb5f6676b608626c8d051abe7795c8435946" - deposit_count: 194 - execution_block_hash: "0x32c72593ddc2739d5eae88440cee25dd36d46751816d1e6f8eed0ef30de97fcf" - execution_block_height: 195 -- deposit_data: - pubkey: "0xa38e925dd3c45de24e8d73e9170168dc8206035ea711741d91a35c24fa71bb7981b89bdca545ee68e8680307a754e801" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x86bb5e37880c4338cf71f6e8712bb644c536d42369c715067ccbf57195781d66d6c321343cc2012d8384d22de7d1b1ff16d9cf7f6c4fbd3c0a5c78bfd15e3c88c3177d54b8967dba46627ab28f2ce2a0c7864d73364be3459fda509352129739" - deposit_data_root: "0xf0715cc57042701d7d885798175984ad28a7034f132792da09509aa9df0ddc57" - eth1_data: - deposit_root: "0x2215ed2c63f505ac6fa32a11161d46f18db19bd56aed182298505b91366adb61" - deposit_count: "195" - block_hash: "0x04f7e15ab5591a7e1931772b057c38e3e385c6376d0e78dc0487be63de1d0ae9" - block_height: 196 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x735a400964fac7b2378423de5960b50eaa02589ac565a271de0129a009f66614" - - "0xf0715cc57042701d7d885798175984ad28a7034f132792da09509aa9df0ddc57" - deposit_root: "0x2215ed2c63f505ac6fa32a11161d46f18db19bd56aed182298505b91366adb61" - deposit_count: 195 - execution_block_hash: "0x04f7e15ab5591a7e1931772b057c38e3e385c6376d0e78dc0487be63de1d0ae9" - execution_block_height: 196 -- deposit_data: - pubkey: "0xa626ce67a47dea646ca21759fb026c6a258cc6ea8db330ee68cbd2455d1c347bbab51f3c08423eedecfa9e36920ef80e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaf63ef6fdc71c6e3adb00df9284be7862cd48ce1a48e8c46b8d2640f72de9a290e160ff1535277594ff03785112e245514287bda41fcde5e4f41099b651467c5e1e580ce2de799e2740a074386c99c7778ef7e4c2a945e40f6e553e152db4f1a" - deposit_data_root: "0x11c9f14e452b20440a56ff34917760d8dcd5a8e630dc7287357181cd486facb8" - eth1_data: - deposit_root: "0x0fa3a4dc90fb01188e00d9df9f45687f0c8a947228db2d315bcf554f2f4244e0" - deposit_count: "196" - block_hash: "0x5c247a56ac7270505ea91e5701c596d8ca2ff96acc269238ad57898ec20b900e" - block_height: 197 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x7a988bd4f4f2f44e82e3b09c2bfc32c9b2eeea372287b0222e2b1d9aaf4aa6d3" - deposit_root: "0x0fa3a4dc90fb01188e00d9df9f45687f0c8a947228db2d315bcf554f2f4244e0" - deposit_count: 196 - execution_block_hash: "0x5c247a56ac7270505ea91e5701c596d8ca2ff96acc269238ad57898ec20b900e" - execution_block_height: 197 -- deposit_data: - pubkey: "0xaae22c365554eb37e2f402f6e6e4bfb0aa9416d8dda0a2f8ea5647ba7dd32023089f4dcc111121e56b52b1fdc29067b3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2f3d0bd66262806b2e9d0683f26016d1b7aa719d3f2739b43516900f126fa7b05f45376bf968769bf9d23cc47124f961960c17f9ce4e1b8f32544aefcedb5711327128e0d54ab20c31c7e11bc6b16dc8360107276385a30180b225aaa3a5cce" - deposit_data_root: "0xdcd3b1b400746a2b3933c4a326f05ae8eab21177b548c9a4b23991d67221db2b" - eth1_data: - deposit_root: "0xf0767d7bb07aac096d4fc126f24e653dae0ed20d2e78a537239aeb416bd9f61c" - deposit_count: "197" - block_hash: "0x9e2658d320b979325eaefb9a6bc7f8bb8d993ffe01dc5048a598f5532a2f7868" - block_height: 198 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x7a988bd4f4f2f44e82e3b09c2bfc32c9b2eeea372287b0222e2b1d9aaf4aa6d3" - - "0xdcd3b1b400746a2b3933c4a326f05ae8eab21177b548c9a4b23991d67221db2b" - deposit_root: "0xf0767d7bb07aac096d4fc126f24e653dae0ed20d2e78a537239aeb416bd9f61c" - deposit_count: 197 - execution_block_hash: "0x9e2658d320b979325eaefb9a6bc7f8bb8d993ffe01dc5048a598f5532a2f7868" - execution_block_height: 198 -- deposit_data: - pubkey: "0xa5c3572c3b770214c14beb4d403d00ffa981480eb850195c99f3b6a2418cef98df28b7f1543d48a2500bfbe25e18ad80" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c50c39e613fa154f1f7b0afd09e716982aa305d5b14fa151a387ecbf172ddb5072311ba32e850725bef0fbacd85fb701822a16e22c408af47500cc33b52c1f40c4ca15d2fb23b90da304e5d4c14ee828ba64556f0e3a4ba95b949ad0b050613" - deposit_data_root: "0xd4386ba3269154bcebcf9be99f15c4a3c5cbb29f1233f4c8392610bf378bc7b1" - eth1_data: - deposit_root: "0xb900dfc08320f9c098a4eada49948bcd336be1f0b018ad9e741049dfb7b8ed93" - deposit_count: "198" - block_hash: "0xb7d727f382dcbb633b6354371ba93f697e4ee8974356879ad1e9071d041cab9e" - block_height: 199 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x7a988bd4f4f2f44e82e3b09c2bfc32c9b2eeea372287b0222e2b1d9aaf4aa6d3" - - "0xd6fd367da921cad3a94901ae5bd5b3cbc3dfd1462326a6d78aa556797d2a4637" - deposit_root: "0xb900dfc08320f9c098a4eada49948bcd336be1f0b018ad9e741049dfb7b8ed93" - deposit_count: 198 - execution_block_hash: "0xb7d727f382dcbb633b6354371ba93f697e4ee8974356879ad1e9071d041cab9e" - execution_block_height: 199 -- deposit_data: - pubkey: "0x8e793a86453b578e9b2f4c252e9ff18a8fb1af36fdd09d9144aea8e2b172738fde30faff3e9391d00827df5efb534821" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x80b57d9e5602fdf0ea42b3ed7eec2f69921264575bd8a63a685cb796db3eb8009bd89f080548ccf9982aca0e475990b90e89e0f2ee025eb3a28f3a928924e79025d6f123bfefc6739685068bb7c41d44c39101612cd65f779c64e9b94b3aae80" - deposit_data_root: "0x705b49ff60a831179dc633062c11329e0fc800bffd91cd2162ee5f9824bd436d" - eth1_data: - deposit_root: "0x90211ebb48dc63aec0f43b619b9de255f716983c02de0f14a5b22e002cf8e2bd" - deposit_count: "199" - block_hash: "0x7889af3b1bec5acaba75ac39a3d3b9e95ca78aca797f305510409b9b468719c9" - block_height: 200 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x7a988bd4f4f2f44e82e3b09c2bfc32c9b2eeea372287b0222e2b1d9aaf4aa6d3" - - "0xd6fd367da921cad3a94901ae5bd5b3cbc3dfd1462326a6d78aa556797d2a4637" - - "0x705b49ff60a831179dc633062c11329e0fc800bffd91cd2162ee5f9824bd436d" - deposit_root: "0x90211ebb48dc63aec0f43b619b9de255f716983c02de0f14a5b22e002cf8e2bd" - deposit_count: 199 - execution_block_hash: "0x7889af3b1bec5acaba75ac39a3d3b9e95ca78aca797f305510409b9b468719c9" - execution_block_height: 200 -- deposit_data: - pubkey: "0x969ca05ea6e49aa9b35d41c3354096f022e9a86718ac454cc8140419dd2ef5c19af37d42733a434a0e77970117ab884f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa809d95bf1d42854575411ce2e535ea3b3cb263c07dffb715044442b196cd701ab25c79a5ae2440783f447c542f518680bbf949ba63ebd1f67bdbe832731a70fcfc6c3b549d2b160239ac852b41b4bd2bd23c88328bcbc6a20590a3fdaa8a3c2" - deposit_data_root: "0xb2150789ea91596d6a1e50fe9841761330fdb7d4a8ad45528f7646684d9cc52a" - eth1_data: - deposit_root: "0xad8875e0af2f6d20643e3670fe9b991c24f3c0fecc85a59a41fb25c0e791fa17" - deposit_count: "200" - block_hash: "0xe9365f98552e964ec79bc2c5d2b0c741b2579c9558480d9ffaa1ee1d3df642b2" - block_height: 201 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - deposit_root: "0xad8875e0af2f6d20643e3670fe9b991c24f3c0fecc85a59a41fb25c0e791fa17" - deposit_count: 200 - execution_block_hash: "0xe9365f98552e964ec79bc2c5d2b0c741b2579c9558480d9ffaa1ee1d3df642b2" - execution_block_height: 201 -- deposit_data: - pubkey: "0x8b4ff71ee947785f545c017bbb9ce84c3f6a90097368cf79663b2e11acc53e18e8f7159919784f4d28282cb39a7113f7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4d04ea08df7c81fd63b7c08aabfc91858eeeebf022ee8dc1eb85b2d092555df04d55cc4c1c4994dc040493b1e310ea10afaf3631c04556b99c22cca3f4b51fc07b63d8eafc136d9dc25729de2b157493277de3ce6427a7828c7aee03ea73c54" - deposit_data_root: "0xd543fb17cd7258052cfa9c1f3fba1a8a858ca4228f5d612b517030a0bf95badd" - eth1_data: - deposit_root: "0x15a652c1b3b70a8a66292accc0392096462bb6655e9d8528afb93d25732e75a7" - deposit_count: "201" - block_hash: "0xae4a7f8e521201b9b74056012a8bdb16a12ca537009ee7b2825cbf5277efbcd6" - block_height: 202 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - - "0xd543fb17cd7258052cfa9c1f3fba1a8a858ca4228f5d612b517030a0bf95badd" - deposit_root: "0x15a652c1b3b70a8a66292accc0392096462bb6655e9d8528afb93d25732e75a7" - deposit_count: 201 - execution_block_hash: "0xae4a7f8e521201b9b74056012a8bdb16a12ca537009ee7b2825cbf5277efbcd6" - execution_block_height: 202 -- deposit_data: - pubkey: "0x984a6c8f4b7545aabbe9e0341c5ff990a05508c94e3757da474daf1d70124c8213ba2457718ec2d1fc562fc0bb36213d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaf9e0247ecdab1f08ffeabf0b753f08007a14506b72377dbfb7d08069b43537995bee0d17a36be683d1d76d1f31218760ed3d72a992bcc4b8e33e1fee223c0614cca7f850d80e651628a3f1f0a129e5a2154e87e6f4c98978063f329eaeecf6f" - deposit_data_root: "0xd4ff330e73b1afb26efcd9f7db884f99e14121b778ff54ed0f4cfdf5585d821d" - eth1_data: - deposit_root: "0x9c5993864595eabaeff8b60957115c80be20df7e4077c9799bc2718b85d31af4" - deposit_count: "202" - block_hash: "0x034d3912b8d10a6c82bff870992b671dd6053602d8b2ec63f5da2d533bbec9cd" - block_height: 203 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - - "0xd74dd49dc28c9adbab7fb5dd05c11f09a6c2ef7289e6d45b56462a2dc7879a0e" - deposit_root: "0x9c5993864595eabaeff8b60957115c80be20df7e4077c9799bc2718b85d31af4" - deposit_count: 202 - execution_block_hash: "0x034d3912b8d10a6c82bff870992b671dd6053602d8b2ec63f5da2d533bbec9cd" - execution_block_height: 203 -- deposit_data: - pubkey: "0xa14cc20155f6fce0f248f9c306a32cbff425272829f7d920073c599b48168fb018c82b2aaf7cbb8b5f6449340023c37b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa993e3a47f18a67b4c866ef09bc402471a32422eb2181c53dfbfe6636f8a67bda4ba86ed22d3c480f796f70a56c6b20914c4b80a014199f8b35ea115a9f6f2539283479d545d9a33dd5f6babc3d861d5b03cec90bac204239f074c08dbe9cce0" - deposit_data_root: "0xd1a0715f2f749515ed034ed6a16583a2ba0276f53db1a55aa6cc9b3d795e99b5" - eth1_data: - deposit_root: "0x8d2051b62cacffd2a701ca5212d26832f0f4e0dec045d5c9a996fc1f3e3d4afa" - deposit_count: "203" - block_hash: "0xd4833843972af8732196ca814e0ea093f8113431a00b6173b899a1907f449971" - block_height: 204 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - - "0xd74dd49dc28c9adbab7fb5dd05c11f09a6c2ef7289e6d45b56462a2dc7879a0e" - - "0xd1a0715f2f749515ed034ed6a16583a2ba0276f53db1a55aa6cc9b3d795e99b5" - deposit_root: "0x8d2051b62cacffd2a701ca5212d26832f0f4e0dec045d5c9a996fc1f3e3d4afa" - deposit_count: 203 - execution_block_hash: "0xd4833843972af8732196ca814e0ea093f8113431a00b6173b899a1907f449971" - execution_block_height: 204 -- deposit_data: - pubkey: "0x89737986b89c213367ab0fb9a4eb998d9b0b713143cc8b0f209c9355607daa4f6e6c925dac3026890e291a9480463395" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2a249d89d206c1e335bffc3838497ab0a8162bb827aff7ced1f08b819ac0e13583a6857eeb5c6eeeead7510062febaf0e142ab4c56a00802bea141e09e8708e750e3049cdbe91bb1448ffa1d2ec4f2c09a1626a774ceb0d0cbfe2e3a3c86207" - deposit_data_root: "0x75b338b16d27eb10fbd28510660fe5816d30c5b2770abf69bf55aba29378746c" - eth1_data: - deposit_root: "0x1a2c95f781f6e6bc105146aa1405aa3a157e835e3159aae5e06a90fdb5e2c3e5" - deposit_count: "204" - block_hash: "0xadeb61523c297ae6488924eeb397706fbd36b90460ba258d39eddcc162bd32b9" - block_height: 205 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - - "0x81af419186b40e88c6b75d4a7e50d2eb478b523a890245e90a5a8666c14d8920" - deposit_root: "0x1a2c95f781f6e6bc105146aa1405aa3a157e835e3159aae5e06a90fdb5e2c3e5" - deposit_count: 204 - execution_block_hash: "0xadeb61523c297ae6488924eeb397706fbd36b90460ba258d39eddcc162bd32b9" - execution_block_height: 205 -- deposit_data: - pubkey: "0xacd8a33698c0b95294943f0642c8b8919bdfbf1e92c61f26107f0f9ad989497742b58363e3d885e4d41a3bcfcc9c073c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x88acce1b8ddba924e58450a9bbb256fc8cde985dcf0c13fad60a248adc6bcbefd26846db5bb49401baef27fd3599074e09a7b18cc4c7b509585febf85dc56d346462b209359ec2cb16d2684a23d744ef4fa0c24e25078408b73f31aaec0256a7" - deposit_data_root: "0x96fc4bac61a6cd54baa17b7434ff2d25eb1d0e3442a602af6d484cb1db9cc6e4" - eth1_data: - deposit_root: "0xd50f56058861337da0c947bcdb625f96a40d3ca4621c58b07eaf8f311aef09db" - deposit_count: "205" - block_hash: "0xcc2b772737ecdf295382ee2a1ee8894aa42f33bea847e41e308728a0f31ed472" - block_height: 206 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - - "0x81af419186b40e88c6b75d4a7e50d2eb478b523a890245e90a5a8666c14d8920" - - "0x96fc4bac61a6cd54baa17b7434ff2d25eb1d0e3442a602af6d484cb1db9cc6e4" - deposit_root: "0xd50f56058861337da0c947bcdb625f96a40d3ca4621c58b07eaf8f311aef09db" - deposit_count: 205 - execution_block_hash: "0xcc2b772737ecdf295382ee2a1ee8894aa42f33bea847e41e308728a0f31ed472" - execution_block_height: 206 -- deposit_data: - pubkey: "0x964e84e1272dcea0fd79d698c1db18e847a5abe1406ef7988e61f39e3f1ef46371be5c25b6c2bf7e53788730f702b735" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x80f6d4e14705862b42e0ba0ab0a063488d8c2a68a61294029529e0eeccf938a0521d81a06147fc84ed867d1ac90b61b613446206fb88cabb7d1dba91eb9808e0dcc82e474e57be768868aa76e2be21dc777ce6ae0c083fa2ddc5abea3325bed0" - deposit_data_root: "0xb0cb5384d711a5179c1c0fae14f4b843321abdce75b4203796e838cb6bd8f9ef" - eth1_data: - deposit_root: "0x750757bba12b4098e68006661bb2504a51ecd34f699881f9ed0191f0ff10f694" - deposit_count: "206" - block_hash: "0xc030e84c7c82778ecb278a7970d6f0d1b453a126fc97b57b964aff30e0f4d0c3" - block_height: 207 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - - "0x81af419186b40e88c6b75d4a7e50d2eb478b523a890245e90a5a8666c14d8920" - - "0x177af982879e5f9786c25fcc42e0a20e48e09419dda7d03e71c6fe190adaeb5e" - deposit_root: "0x750757bba12b4098e68006661bb2504a51ecd34f699881f9ed0191f0ff10f694" - deposit_count: 206 - execution_block_hash: "0xc030e84c7c82778ecb278a7970d6f0d1b453a126fc97b57b964aff30e0f4d0c3" - execution_block_height: 207 -- deposit_data: - pubkey: "0xb406b1521362c206669d15b4e5448aae2f1854c707592abc612973b4726d47edf3bcd9ecea1a4b6cffc6a9f7b039921f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8122c635f73ce71fa05d9680c5c48c39085864af2e811c324c233c73fd12aec80390bb306a6103bfdada9a2679874261462273f765cc1c5ebce0dff42cd9e80ff21fed9fb53e755602db2646742013a4e6ea25470da6fc11a3ad90b06651f43" - deposit_data_root: "0xf74d5d7d3af4a3bf5f6695b1f34ab4d5336c9ef290b8a1794f44db60a1d85b7f" - eth1_data: - deposit_root: "0xe39b947702ff8af4cc4e50acaa3fc0d9ab834dd60237e134122f4a5b3690644d" - deposit_count: "207" - block_hash: "0xc3dbea25648a192a58491607281a486afb876647edf3523a4ae7cbebbe7067e2" - block_height: 208 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x1edb1ff214185dbce83d685ff59906b779b360f07616112aa60e2d2dc8508c51" - - "0x81af419186b40e88c6b75d4a7e50d2eb478b523a890245e90a5a8666c14d8920" - - "0x177af982879e5f9786c25fcc42e0a20e48e09419dda7d03e71c6fe190adaeb5e" - - "0xf74d5d7d3af4a3bf5f6695b1f34ab4d5336c9ef290b8a1794f44db60a1d85b7f" - deposit_root: "0xe39b947702ff8af4cc4e50acaa3fc0d9ab834dd60237e134122f4a5b3690644d" - deposit_count: 207 - execution_block_hash: "0xc3dbea25648a192a58491607281a486afb876647edf3523a4ae7cbebbe7067e2" - execution_block_height: 208 -- deposit_data: - pubkey: "0x8ca1bfff2cea25ae77249cb18146a39b9630be5947b65d15044a5e5816b6d29dac9da83504083bb8e87dbe97dfb6451f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb83a7cf087d9dc1d5d587b103b84f2dbbfaa07d9385861937d2733eacb7c1e291336214aa6eea4b38377fb2a2a7de2170edf187e150125be3d3f8dee35f71b86be7bf4e8b390bfec55f15330dba9eefa1003fa9cc5122fa5c97d8d699f2e0a2b" - deposit_data_root: "0xa0a33dd10f90a1ae86de6926edb1c128943a276b0532426521d187c1d5d363c6" - eth1_data: - deposit_root: "0xd2fe6e21e57ccfe1ededd19b56161b95e46022936207835b5563617107574558" - deposit_count: "208" - block_hash: "0x04a5433ddf3ad6d7dc52c745d96e863204f6f5add434e7d3a34cef73dd92e34e" - block_height: 209 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - deposit_root: "0xd2fe6e21e57ccfe1ededd19b56161b95e46022936207835b5563617107574558" - deposit_count: 208 - execution_block_hash: "0x04a5433ddf3ad6d7dc52c745d96e863204f6f5add434e7d3a34cef73dd92e34e" - execution_block_height: 209 -- deposit_data: - pubkey: "0x8c2e07abba50e0e1c624bae0dfff418f8f00502e377840f72e067cd33917a209c525fea7dcc7c44f0195a3b9a5dabbb5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b9f750ad5ca21b49bee7a975da855675f22904ed9bfc52d8a9491e6d96e8ba58fd67270d3084e91e3cc7ee7321d683d10864550590818c393d4c2a0003926e2e5a73164336c216babe5a0fef22ed447aa51c67cd4704762da4302e1f7d3b49b" - deposit_data_root: "0xbdab20d7935a00c76b38e589b2073a6304eeed96e201423a7e420619edbbe893" - eth1_data: - deposit_root: "0x99fb82c1633a127efca032676fae7239a258b230c9323ec75644c7e659cbb231" - deposit_count: "209" - block_hash: "0x4d98b647dfe4547b79591778779136b4586160a163f5af4162fff546817ce4c4" - block_height: 210 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0xbdab20d7935a00c76b38e589b2073a6304eeed96e201423a7e420619edbbe893" - deposit_root: "0x99fb82c1633a127efca032676fae7239a258b230c9323ec75644c7e659cbb231" - deposit_count: 209 - execution_block_hash: "0x4d98b647dfe4547b79591778779136b4586160a163f5af4162fff546817ce4c4" - execution_block_height: 210 -- deposit_data: - pubkey: "0xa4c11513391dd190b1ac0cf8a1c2c1b9c39d925b38aecb950d357283beee49ab97b1d7dfb34396bcaf1c25658fdf7713" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb82675644e2826c0d7f77f6e4579d2ba98d37330591e05c3e87c5de32a8c97ff1fa48902289ec52d990fb81d2172a5590c569bdc5da0f0f7b9e7e07d86433f54197a37457ad65cec11b8028e88c8e175e45530910925fa6720be77a7b5323d8f" - deposit_data_root: "0x49b40e35773ce653a0d51a6a45e20d9490df06160eb3059830a1f7ac9fb7393a" - eth1_data: - deposit_root: "0x10530b32816c9a5d1213ecba4ea82e0746d706721bd5dd14441b32050a6c33e4" - deposit_count: "210" - block_hash: "0xf35c48ec777e6646c99c7e1a873e949bf426354e55c2f115fe9670914e249af4" - block_height: 211 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0xa94a6052c766a8599b6c20b938f754be5554f1b7a7ce0c8d337160a8b5c5fa68" - deposit_root: "0x10530b32816c9a5d1213ecba4ea82e0746d706721bd5dd14441b32050a6c33e4" - deposit_count: 210 - execution_block_hash: "0xf35c48ec777e6646c99c7e1a873e949bf426354e55c2f115fe9670914e249af4" - execution_block_height: 211 -- deposit_data: - pubkey: "0x92afb506a8345b325a4fc1cf1135455eac709fe1c623bd8f0972cd9e51a763cc1e80bab1f7e01976d1c4f13af3c57caa" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xabd9696dd03623a4c7ee8eaba9dd7d40a3e2658d14c46ad3ba0e07fb6a8003788a6b22cde39b54b38e41549d4b278b270d4423ae1cf28228b92d29763200a97e454f66a2eb84e4596fa5b1776d288639c6ba9e2b8f07c6695e25d6369f59d9f6" - deposit_data_root: "0xeb7df9a089860361315c6c55ed2695cad55caa92a751329b1c7f07e2c8055ccf" - eth1_data: - deposit_root: "0x30c2e1bb452f52ccf55edb7abc1ec4774dc3058e166ac655d79640f1226a7943" - deposit_count: "211" - block_hash: "0x7fa99b674a69b2752edf65932a26abdb23109fd68632de579ee075271fb91e80" - block_height: 212 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0xa94a6052c766a8599b6c20b938f754be5554f1b7a7ce0c8d337160a8b5c5fa68" - - "0xeb7df9a089860361315c6c55ed2695cad55caa92a751329b1c7f07e2c8055ccf" - deposit_root: "0x30c2e1bb452f52ccf55edb7abc1ec4774dc3058e166ac655d79640f1226a7943" - deposit_count: 211 - execution_block_hash: "0x7fa99b674a69b2752edf65932a26abdb23109fd68632de579ee075271fb91e80" - execution_block_height: 212 -- deposit_data: - pubkey: "0x978b5194d96515146754465533db2d854e58371f26e78fe0eebc5c5b65917a3611947cab3bf7fc645e3cb870e4826019" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa7a1cbd47302acfa136a0114abe23123670fa4ad08e76fa4bc86007e6d941c71ce51dd92cbf1bf0d8836b4ae49b762a609559b7e798e3652a1eda20d89ace8606fff2ca5b1fe83c7783bed9267a18cc29e119c361329c574cc64fd8d209221d7" - deposit_data_root: "0x4715ee6c9d226f8fa5da1140ef6cd9f7f2f67a417c24498377025d176df07ddb" - eth1_data: - deposit_root: "0x64f8471778ca893643db2d0be9bf4ba437611a0bc61cdcef451da8250e30baca" - deposit_count: "212" - block_hash: "0x0176fbf110c6da3ef4495909cdc1928eb1d9c77a7f41444b89c73dfaab805caf" - block_height: 213 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x80be627c56fc721728d409e5f72984eb86cf51276c72be17a7def353b43a0c36" - deposit_root: "0x64f8471778ca893643db2d0be9bf4ba437611a0bc61cdcef451da8250e30baca" - deposit_count: 212 - execution_block_hash: "0x0176fbf110c6da3ef4495909cdc1928eb1d9c77a7f41444b89c73dfaab805caf" - execution_block_height: 213 -- deposit_data: - pubkey: "0xa3f841f04d3410b06a4b4eb31b3fd92eadcf486af60986723d34ef7b8a9908541ab6e7e0ecf421f593f86afdf8cbe894" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa810363310e7254b59997009c7b4739bf34856e5b16b7bbcebc61c23efdf727e36d9be21eaf4cffd2eaf4563d9fe6b060eb81f52882047c188136b132f34cdd5d9b67b04acdce7684aa16e9e378de9162c5886bb48cfa76435655b099d5190a7" - deposit_data_root: "0x8a2db165a79f35c68b6f84336b364a56a68eb231908eec22d8b32dd85fcc731e" - eth1_data: - deposit_root: "0xee9bc848d593ff36992ed82fa51763caa78d4e5efd3a1fc7ae9f0fa7e19a8e94" - deposit_count: "213" - block_hash: "0x51c836750936babe4616f3402135e0ab0dd470e1151c44879a4d7662b9b08be3" - block_height: 214 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x80be627c56fc721728d409e5f72984eb86cf51276c72be17a7def353b43a0c36" - - "0x8a2db165a79f35c68b6f84336b364a56a68eb231908eec22d8b32dd85fcc731e" - deposit_root: "0xee9bc848d593ff36992ed82fa51763caa78d4e5efd3a1fc7ae9f0fa7e19a8e94" - deposit_count: 213 - execution_block_hash: "0x51c836750936babe4616f3402135e0ab0dd470e1151c44879a4d7662b9b08be3" - execution_block_height: 214 -- deposit_data: - pubkey: "0x81fba887a59873ac21711818cc8a63b2d4be1a69627f8c70586a56328a96aff64b880f1a07c125eda24784dfea0bacd4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x93595db7bb94204b0825b54a3feaa6fbbbd2b7b2500eff772129f5d1633e5d3aabf48d138d91a11f3432fa45d49a7a2708ff91c8e53bffd0da37f03b03aebe01626d67ae5388a38325056e0ee9419f511520511fc772a51f4d512df91d562f24" - deposit_data_root: "0x25cead55a29f7e5da33987cb5519bfbca4e10d14b747ec5d0e0a5d20f92a59cb" - eth1_data: - deposit_root: "0x0a6f0bafcc040a1296ff468e4ee75a20d5d26cc395113fefb164f4367ba017e5" - deposit_count: "214" - block_hash: "0x16b04fa33d14e124eb5a40a6af1b01daf48c86937982ae4d208f63bfbc4c7d7a" - block_height: 215 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x80be627c56fc721728d409e5f72984eb86cf51276c72be17a7def353b43a0c36" - - "0x3785e11a23b4330f60f96e5dad69c55dab1a4c26c521e1c02f31f53639928bb7" - deposit_root: "0x0a6f0bafcc040a1296ff468e4ee75a20d5d26cc395113fefb164f4367ba017e5" - deposit_count: 214 - execution_block_hash: "0x16b04fa33d14e124eb5a40a6af1b01daf48c86937982ae4d208f63bfbc4c7d7a" - execution_block_height: 215 -- deposit_data: - pubkey: "0xa9da8f5d1d62844df8f6fae763aa653127d6eaad1b4d8ba0b3b3417e9c486be3cc879ebad7fb182dcb364d3a292ab07f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8748cde6cce3b3b7ad6b0a606e3bbefcfd26794817699117b7ba4993df77ebb656954eab6d0563447620584f04d08fd7106462757153f4ee774ebfc316c9a79836dc02beae801f29c1da0933928a8fe0f326a01538e3e32a6f10f728edb34eac" - deposit_data_root: "0x884ed496077a53a281f0d0b56028a6d3947753cee6ab57d9bbb71cd9bee577db" - eth1_data: - deposit_root: "0xdd69faeaad85b4e2093afd6b377c9bc01987a0ae95ce1ca8f7dcc05407cdf147" - deposit_count: "215" - block_hash: "0x7b4865729e81f44da4be1ba22a9dcc7e422c9532974f5a9062ec67ac9bd62cb8" - block_height: 216 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x80be627c56fc721728d409e5f72984eb86cf51276c72be17a7def353b43a0c36" - - "0x3785e11a23b4330f60f96e5dad69c55dab1a4c26c521e1c02f31f53639928bb7" - - "0x884ed496077a53a281f0d0b56028a6d3947753cee6ab57d9bbb71cd9bee577db" - deposit_root: "0xdd69faeaad85b4e2093afd6b377c9bc01987a0ae95ce1ca8f7dcc05407cdf147" - deposit_count: 215 - execution_block_hash: "0x7b4865729e81f44da4be1ba22a9dcc7e422c9532974f5a9062ec67ac9bd62cb8" - execution_block_height: 216 -- deposit_data: - pubkey: "0xadeb5ccef7679c5d26e97ac5d2c8222058bf8b7c5eaebd31db3f3ecbb8d00987e2b921711dc6953eff6852601c57b198" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb6b988317f7e9f48abbe85d6719ab37ed5abc8e98d31a4a37120767a70b9e1cf4a5e3b391b9941a201cc7a1f004fa97a0a1e3c8767e0fdac133d9292a8eb6da59e5ab28473bf87d0ea514ac69c60f1b56f9d31aea615fa48ac7feb17e1ebb4b4" - deposit_data_root: "0x8699f7dfd9049a9383abc845a33cbce20d6bccd88b08cfb4166e298ead4e2dd9" - eth1_data: - deposit_root: "0x5bd775b2a763bac39331c22c5a1fb189221e22f7be74fb91dc7de0eeb7fcbfb6" - deposit_count: "216" - block_hash: "0x3660dc622cb11cd94846055fbeb73df41c3a088242dd96c2e5159de3f1cadc48" - block_height: 217 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - deposit_root: "0x5bd775b2a763bac39331c22c5a1fb189221e22f7be74fb91dc7de0eeb7fcbfb6" - deposit_count: 216 - execution_block_hash: "0x3660dc622cb11cd94846055fbeb73df41c3a088242dd96c2e5159de3f1cadc48" - execution_block_height: 217 -- deposit_data: - pubkey: "0xa4e2f5a419590f3d30cbcf9cac0b4a89b636458dd6d38aa763694d4edd787056536089077d8e71cc4b182f8e87dc7916" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa4c51bc0d55bfc64de2f5123bb685777f33784bd632b61e65166ca8eb8084d8effe9e93e4a7359b88f4d03644fba521028e45f33996189e216637655ac2de55dcb0266289b81cd8b4e83c9e2bf0b7bd53d95942f66c7ee9d85776c8ff7af6ce" - deposit_data_root: "0x1c3dd97d82fd3cc85dfedad67f70b39cd7bc2df5d376eee91cdb3137e1f420da" - eth1_data: - deposit_root: "0xebbdfeb489d87fc3cd7e79ae85adbb1ca2237cc64b6e4d37a6de996f32f6ea3e" - deposit_count: "217" - block_hash: "0x861a80af90e9a9b72718b0f49dac71c9c100cf0d00ecd0816b52278ce534e106" - block_height: 218 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - - "0x1c3dd97d82fd3cc85dfedad67f70b39cd7bc2df5d376eee91cdb3137e1f420da" - deposit_root: "0xebbdfeb489d87fc3cd7e79ae85adbb1ca2237cc64b6e4d37a6de996f32f6ea3e" - deposit_count: 217 - execution_block_hash: "0x861a80af90e9a9b72718b0f49dac71c9c100cf0d00ecd0816b52278ce534e106" - execution_block_height: 218 -- deposit_data: - pubkey: "0xa365251e868fae8780f009c14c7ddc349389230b75c79e11628b798dad880d9704a10f3358bea40f4136fe37fdec13ca" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8ec3bda16c205360636e42958f2076ed5e245cb48f3ea395d7680c689f62eb2bce4839b78129884aac0dab5345706f560310654681b3542f364b7aa4e426109ec7a772070be08997e80a890e2c1e8a4606f50cddc1fa8298ccb038719561bcc6" - deposit_data_root: "0xbc2cf0159a36545c3dee043c7c612029a41ba7e4ac055d4c63ec9eb5b6564738" - eth1_data: - deposit_root: "0x7bc4420c8f3a21b553c643f7f33d830cfc6a658e9c5e53c24828403d3f487b82" - deposit_count: "218" - block_hash: "0x1fbb6ad18010df3607676411ede951c80c862b09db67219380cccdeed7d01422" - block_height: 219 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - - "0x555908f2ac7299a40b5bb9269ac6d4ea86a65af7b648d63181f59cfa8314d17a" - deposit_root: "0x7bc4420c8f3a21b553c643f7f33d830cfc6a658e9c5e53c24828403d3f487b82" - deposit_count: 218 - execution_block_hash: "0x1fbb6ad18010df3607676411ede951c80c862b09db67219380cccdeed7d01422" - execution_block_height: 219 -- deposit_data: - pubkey: "0x91ad339cd616316e79470c8695cd83b3811eb762f292c9a67c802b84e8e074ef5d208704dc1d5fb4a8343e0e44b25807" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x85d02a1704c8fcdcce59ac1ec6868f282d66839ea77e3673cabcbc178b8cefa93b4dec28167f38a83dacfed4b543f1260375c743bd25e015d8450ae0503594afd8eb394411dbd377f67b16436b6fafe1133e5012c474158aaf2881c040f27716" - deposit_data_root: "0x0407440d40aec95c78548e7f9cfe22144f11166bd468e553a150733962098bd2" - eth1_data: - deposit_root: "0xadfa2dc986c1769723c3007c92843f9fba2a30d162548bfd2c5073a0520ef59b" - deposit_count: "219" - block_hash: "0x274170cd5f2790ae9938185e411eeb46d258cc7a1d13a3db5be028cc55a832ee" - block_height: 220 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - - "0x555908f2ac7299a40b5bb9269ac6d4ea86a65af7b648d63181f59cfa8314d17a" - - "0x0407440d40aec95c78548e7f9cfe22144f11166bd468e553a150733962098bd2" - deposit_root: "0xadfa2dc986c1769723c3007c92843f9fba2a30d162548bfd2c5073a0520ef59b" - deposit_count: 219 - execution_block_hash: "0x274170cd5f2790ae9938185e411eeb46d258cc7a1d13a3db5be028cc55a832ee" - execution_block_height: 220 -- deposit_data: - pubkey: "0xa2d7d0d6f85894bda115022811b35ed5689143187a911fee5bcd0d6b1c78f4db3542223f496024fb2279b8ee76b5ea79" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa3d3e4034783206e696e6ed0fe1bb3d85b3ff78892422daf8f73b60c3526e862463c0ebec3f57fee778adadac8c4e096016430f0e39659ce5cb4ff72e377ef585b0e5fc06502fe030cc19c6ea3b63c475c4f151c698758f3ab55c6a757ca004d" - deposit_data_root: "0x4ff35fdd7abcb307af9cd011f196a1dcc2383905c9f5267ccd8e12729932ded0" - eth1_data: - deposit_root: "0x6c54753ac7f623632b80b673c081a8aaef63506b1e4125e3d37557e191c911dc" - deposit_count: "220" - block_hash: "0x75b5f98560bcca1d3ef1f0ec2409dbe6ade0e7c167446aeaaa2834c4edbda673" - block_height: 221 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - - "0x4615edc70a037bfbbe30e36784fc579c02930a553305855c6201fa479683dbd9" - deposit_root: "0x6c54753ac7f623632b80b673c081a8aaef63506b1e4125e3d37557e191c911dc" - deposit_count: 220 - execution_block_hash: "0x75b5f98560bcca1d3ef1f0ec2409dbe6ade0e7c167446aeaaa2834c4edbda673" - execution_block_height: 221 -- deposit_data: - pubkey: "0x80029a26a45145f67892bfb25783d04f700e8917afc2b406695d4443fc3a45ab9c2c0572c357a33a9ed3877ebc479822" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb7f1069867daaee8b61e9e4494feb875e7192624aa37058b3862f796ef13e72df222abc75b902324af95af6baa62b5c40fbb70f59aeadcd4eb4a22a2591aeb150e9b04fbce014b4bebff2af4a7f38aa17ceb65c01cfb2aaab6f290703d12f878" - deposit_data_root: "0x8d9db83da28ca8c0edf22485af80afc1f19b144a3b44797cff3f9b11fc580f0f" - eth1_data: - deposit_root: "0x994cebba4398a53a0b2acfe1754f7e26ad34f11cbdaa520bf72099e5ba45cfcb" - deposit_count: "221" - block_hash: "0x9f3818b078a804575ae9a09ac84ef7268274b564cb832e78662d8febb4b9e81b" - block_height: 222 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - - "0x4615edc70a037bfbbe30e36784fc579c02930a553305855c6201fa479683dbd9" - - "0x8d9db83da28ca8c0edf22485af80afc1f19b144a3b44797cff3f9b11fc580f0f" - deposit_root: "0x994cebba4398a53a0b2acfe1754f7e26ad34f11cbdaa520bf72099e5ba45cfcb" - deposit_count: 221 - execution_block_hash: "0x9f3818b078a804575ae9a09ac84ef7268274b564cb832e78662d8febb4b9e81b" - execution_block_height: 222 -- deposit_data: - pubkey: "0xb6c8c112c7802a29579e14b228ae6a33fd7f0a3d33d06708b0f07cf7ec18f48e5ed7d5c47e7942c2aea2d1f4dd95557b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x82d56bd4b5cf49b4aa037a0fac68e16f6285b7e2156e52267b15a0f424c60f6df11456d6ee28dd6e48782c52fdae8690062b82574903fe2f1a0b6a502f9667957022fb4685f9d77576cb580148448f1a8d223e2d3809c55d52314b33033053e7" - deposit_data_root: "0x9c5e003d673975a5d0dd59c797238f6699c03098dafaf05c841725e13f2211e7" - eth1_data: - deposit_root: "0x3190b4adb59dba145f8bbcc6e138e92173e69a57b657bd09822f0dc4699bbd6d" - deposit_count: "222" - block_hash: "0xd35aa31fa61ada5bdfafd207b08316b9cf404bbbbb655756be1654367a556dea" - block_height: 223 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - - "0x4615edc70a037bfbbe30e36784fc579c02930a553305855c6201fa479683dbd9" - - "0x9d39bef25d1cb967f1c2afc70f08d3b10fa353154698513f0516f4a2326ef1c2" - deposit_root: "0x3190b4adb59dba145f8bbcc6e138e92173e69a57b657bd09822f0dc4699bbd6d" - deposit_count: 222 - execution_block_hash: "0xd35aa31fa61ada5bdfafd207b08316b9cf404bbbbb655756be1654367a556dea" - execution_block_height: 223 -- deposit_data: - pubkey: "0xad4beca6ee84273739c03792e0a2096337c92fb096f7a902e0b09b8bf38d3f67c27a00b05d3ca69a0c9a43b491e8af4b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83afd2f9eee050a9e4f27651dca18b4ac1a4a48523aaa8d41fd7c87b26e288a07e4ff33ffd243fc053204b10a9abe3cc02b014ab31cdea51e94094d670dc9f6c830dc0c04f078cfcd74cb3cafa0bad02d99433d67a2377b53908062267573dc5" - deposit_data_root: "0xa2c62c2afa8009d90ee3f7ded14aee4bf2d35ecea17026a011447c433f42986c" - eth1_data: - deposit_root: "0x21511637fa74488b88e6b0831a941619772a0679c69c43f0c7a91ec5ecda1b6b" - deposit_count: "223" - block_hash: "0x4320d44ec19761231557697186b0c02d807a9980a0a55c0af4b308bf9276fb65" - block_height: 224 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0xdc8256156a85778064cc0f46fab037bebc4ea0f98abe602270d370a7938cc711" - - "0x500bcd7598a4378a5c0ab9ae9ed217c3ca4c47ae0e0d34b7f5d9c987780cc413" - - "0x4615edc70a037bfbbe30e36784fc579c02930a553305855c6201fa479683dbd9" - - "0x9d39bef25d1cb967f1c2afc70f08d3b10fa353154698513f0516f4a2326ef1c2" - - "0xa2c62c2afa8009d90ee3f7ded14aee4bf2d35ecea17026a011447c433f42986c" - deposit_root: "0x21511637fa74488b88e6b0831a941619772a0679c69c43f0c7a91ec5ecda1b6b" - deposit_count: 223 - execution_block_hash: "0x4320d44ec19761231557697186b0c02d807a9980a0a55c0af4b308bf9276fb65" - execution_block_height: 224 -- deposit_data: - pubkey: "0xa9ec8e4f705f27e6991a43ce006155825776666664d03be89929c4e5c9a01c7b759c1751e16b7a5d12084c419e97878d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa7b8b994844e1f99366d6b36e6802dc9e9c9e93898bbe2856b17b47f3ee086dead8413745f983d2d02aacbc58c623faf096201428d8cb54a5632334e9e486279e1c055fbb7b993827ee6823dbd633b3f5adc742133f2835470e2c047410d0218" - deposit_data_root: "0x757bda9525f7bf25b15158d2eb44382b9271d489703752c15c79f2022e8bf9c0" - eth1_data: - deposit_root: "0x7abe7feec57fc014c31f919a1706868a569b15e5721201500f8a502f2c38ca73" - deposit_count: "224" - block_hash: "0x2e1d1bd56695914502d3d69e874b7969503fd2bddd90402671ae2e58210e30e4" - block_height: 225 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - deposit_root: "0x7abe7feec57fc014c31f919a1706868a569b15e5721201500f8a502f2c38ca73" - deposit_count: 224 - execution_block_hash: "0x2e1d1bd56695914502d3d69e874b7969503fd2bddd90402671ae2e58210e30e4" - execution_block_height: 225 -- deposit_data: - pubkey: "0x801a35e02410c44a3d81b564980738fc5a1d4d15b887c9477c8835c9038233fceddfac4c572226599b25fe3faefa85b1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xafcafcad14dcb0b9aa9cbd21fefdee102e9da1439d676e7721edd62641b218dcb43264f361e56e2aecaffc31865c0f9304c7ebe5ce98f2c8dc230029bc681d9282b80ee0a4b1f18e8bdbcb80e6963608bc9d02182afed40ae381347577395f9c" - deposit_data_root: "0xb0c1df18776f2378e753ae8937bce428eeed11559e3e00da552ad63fcebdefca" - eth1_data: - deposit_root: "0x3f5c02cdafa88b492e4e0e4a424dbe520959a048044e2c214460d9aea2fb203a" - deposit_count: "225" - block_hash: "0xb382a1b5dbb7b22d33c0000937f48cc9d2a1902576c2f789b2e13eeac8e295d0" - block_height: 226 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xb0c1df18776f2378e753ae8937bce428eeed11559e3e00da552ad63fcebdefca" - deposit_root: "0x3f5c02cdafa88b492e4e0e4a424dbe520959a048044e2c214460d9aea2fb203a" - deposit_count: 225 - execution_block_hash: "0xb382a1b5dbb7b22d33c0000937f48cc9d2a1902576c2f789b2e13eeac8e295d0" - execution_block_height: 226 -- deposit_data: - pubkey: "0xa38b922e79533b5fbec5f710ad90837cfce8073c982c57597027e5b6facaabc1edac06c7014fb1bcf8e11aaca4049dbb" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x813753be7c3419e592c33627668db7e74bae071c775b069194853355f9b91755d7dcc77672ebc011f313c8e3dafecc900f63260e6247d7a1b3c08ff3a6f1c9ff5b805b073049d1d197867d66cf9d356a3f6200041b4722d72564aeb29a0d4a7a" - deposit_data_root: "0xb7c0ab5e3995215280d9af900c21a86dbb5d2cf9ff454f7a762b699358e9558c" - eth1_data: - deposit_root: "0x3e59ddbdc9744648e98694f1fb660d80bed43d554cee48dd274b482847d8c1c7" - deposit_count: "226" - block_hash: "0x6a3c9dafcc60d22e4747369ddd595545dacdf29926f2d3d6d4344ccb76fd8760" - block_height: 227 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcb67f19e86fe3c871884bd661d1a82b3780ca2e36b225490607b2a6eaf66a552" - deposit_root: "0x3e59ddbdc9744648e98694f1fb660d80bed43d554cee48dd274b482847d8c1c7" - deposit_count: 226 - execution_block_hash: "0x6a3c9dafcc60d22e4747369ddd595545dacdf29926f2d3d6d4344ccb76fd8760" - execution_block_height: 227 -- deposit_data: - pubkey: "0xaf4cb80f788305b5c22f5a16bd3f5d2f2f50a403e4171b78e267a4e2d15b14beb04735b9ec206751a8163dae9ef3e96f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x828d01fe50f60180f2639e9337a79c130615172a25692bb29c0e21b7a6644c63820b38840fd2dce4c425faa1c8ae96bc19d7761cf0db0493413ab186acb3fe78dae6c3db6d815d91f817645646d54ea03738db3b2ca686344bbbfadd06299c45" - deposit_data_root: "0xd8a2270f803bc660d2ae17e446ea8a47d59fbac4662232c48d7093b28bf4446a" - eth1_data: - deposit_root: "0xb1d7530096f2c5173ff8c3568bdc8dc0eccff29e5697ee803b488a9b1c89c451" - deposit_count: "227" - block_hash: "0x1c539da3e3764a8e1dd17e19451b72437daeb6f8fe45e592e8d96a7970efad4b" - block_height: 228 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcb67f19e86fe3c871884bd661d1a82b3780ca2e36b225490607b2a6eaf66a552" - - "0xd8a2270f803bc660d2ae17e446ea8a47d59fbac4662232c48d7093b28bf4446a" - deposit_root: "0xb1d7530096f2c5173ff8c3568bdc8dc0eccff29e5697ee803b488a9b1c89c451" - deposit_count: 227 - execution_block_hash: "0x1c539da3e3764a8e1dd17e19451b72437daeb6f8fe45e592e8d96a7970efad4b" - execution_block_height: 228 -- deposit_data: - pubkey: "0x8e0d2214a6f4364590a4acbe3db7b3757b6f2f1eee526babc383e68fee4a7c9735a672f83aff5e9384d66b2fe264c9b1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb07f7ee9cae121e1482ad8cf0855b4ea785bf039f3c09032e417f9b6418b83ad4c7e68f9b68ae015953f5519fd47c8a604d61acf88b0a10c6bb0061f99320be1ea016e3e07e5d6078574ff97d5e9fc315f5156cc6d4f79c541cec94cd5872dc5" - deposit_data_root: "0x6bef5bde9f19342720ad3e132be8e8b9f7bf1508882bfe3f8ab56e1d11faf7db" - eth1_data: - deposit_root: "0xdce8bfad47155a9784cd497dcbc4dc3e96f262261246701c7792d45689a4e44b" - deposit_count: "228" - block_hash: "0x80caca4755897203f034dc16b609c1c2f4564cddad11ba383e79803dfc8b00e0" - block_height: 229 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x5fb54637467f4b1e7fef955014bb48a73937a490562092a7bd1b99dac451fde4" - deposit_root: "0xdce8bfad47155a9784cd497dcbc4dc3e96f262261246701c7792d45689a4e44b" - deposit_count: 228 - execution_block_hash: "0x80caca4755897203f034dc16b609c1c2f4564cddad11ba383e79803dfc8b00e0" - execution_block_height: 229 -- deposit_data: - pubkey: "0xb110c3a2e0100ca6338d66f81c4fa6eb5cdf0aaaf839ea8e0a67645d958fba5f13bba8b66f7f9d069d571f25cdb8e47f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb8381b33e5ebec8010aec019e024668752bcebaa27b7ec938fe9e2a27d13f56c0e14c7fe115583ef230b5d0fb2d9a59c19f681fbc38f34b2aa9f63a58763f9804a01098031e66e2f7399482719c1729d07c25b852ca442dc620a69054de8e739" - deposit_data_root: "0xbde6b47792e6754679c58260381c259bbd16097042b3d1c24404f6618b902e52" - eth1_data: - deposit_root: "0x9be8f309e2977f393617ff08549158f8185a24bf719001070d47e4fc19ab8806" - deposit_count: "229" - block_hash: "0xd9dda4f35c9c3edc515d03b9513be176d9420527d9ca62f2d7f484560d68985a" - block_height: 230 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x5fb54637467f4b1e7fef955014bb48a73937a490562092a7bd1b99dac451fde4" - - "0xbde6b47792e6754679c58260381c259bbd16097042b3d1c24404f6618b902e52" - deposit_root: "0x9be8f309e2977f393617ff08549158f8185a24bf719001070d47e4fc19ab8806" - deposit_count: 229 - execution_block_hash: "0xd9dda4f35c9c3edc515d03b9513be176d9420527d9ca62f2d7f484560d68985a" - execution_block_height: 230 -- deposit_data: - pubkey: "0x879db2b2da4652de6f12a5a9e5f8665daaffa2e4c25fc8609af45b428dd2a35244b66ac2a910dd76c0961e56c660cf59" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa14756fbc7bfaa2c851472eddf1c887d18c23fcbb3d2594e9dc085ed3236ffcd19529ea1905a48a9c666a61c8571958a17bd95397ba545381da98689290fc8e359aaa24397ed33279e739e23ce2eb0f6de4b8c3e5ce1f4d15ed433429300f380" - deposit_data_root: "0xefc249e2faad07106c09333b4cbb73a021b753a9e13c8eb611b495829b22d501" - eth1_data: - deposit_root: "0x71fc589ae852643220368a3ded27a2aced2490b2405e2c7746a1ded7037cf662" - deposit_count: "230" - block_hash: "0x58025631a377c39ec1ff81899fc7574e3e918af2fe5b6cbd52afc269b69f27b4" - block_height: 231 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x5fb54637467f4b1e7fef955014bb48a73937a490562092a7bd1b99dac451fde4" - - "0x00515eda2b4aff0be9224f7d22352562c340830ab164489eee65b44431d623b1" - deposit_root: "0x71fc589ae852643220368a3ded27a2aced2490b2405e2c7746a1ded7037cf662" - deposit_count: 230 - execution_block_hash: "0x58025631a377c39ec1ff81899fc7574e3e918af2fe5b6cbd52afc269b69f27b4" - execution_block_height: 231 -- deposit_data: - pubkey: "0xa4cc78a560437549a4924c1d355e81a3467aaf9d7e7e1b4c7df6b39528345bb950c51c5316abe27f8618e5c7ca7dc5b7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xae747862da82759f24af4f9103c1ed3f87f235a89fa77853050c607e11a10f4bff05de215e6c6aaaf5b9d6b539451d1003c8531ba3bd379edb89a0ee7b5219f011336fd8232d40a10238559263bcfe81da3f31fd29397f8e0b9cff013b9da2ba" - deposit_data_root: "0x9e5b59deb9c620e5218e2064db2836bff8fd7cadcee035ee762f8db7c574cae5" - eth1_data: - deposit_root: "0x30d14127c55bfb5dbcd6adc091e29a155cea9657dcc190ce17d57875fb723556" - deposit_count: "231" - block_hash: "0x6b95be192a6c1967572f0e532c52335f7c273cdbe805373da35ab2ee78f7ddef" - block_height: 232 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x5fb54637467f4b1e7fef955014bb48a73937a490562092a7bd1b99dac451fde4" - - "0x00515eda2b4aff0be9224f7d22352562c340830ab164489eee65b44431d623b1" - - "0x9e5b59deb9c620e5218e2064db2836bff8fd7cadcee035ee762f8db7c574cae5" - deposit_root: "0x30d14127c55bfb5dbcd6adc091e29a155cea9657dcc190ce17d57875fb723556" - deposit_count: 231 - execution_block_hash: "0x6b95be192a6c1967572f0e532c52335f7c273cdbe805373da35ab2ee78f7ddef" - execution_block_height: 232 -- deposit_data: - pubkey: "0xb0959a30fabda6f21ccfff1b9a4854fbd869e767b253c2f9bc08c4cbbaa3c24f37a7124dd8e67e2b0bdf4b052c2c9873" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb64885eaff19de38042492a996f157cca5fcf0194227e2d06b6aac66c8dd851806072acc35516a314384b5a11acedc2709ff085e2a236ee9b1a7c2247d229fb709c012daf6d0b3a8e1cfcaffd6f7d205d8feb2464cfa4d2c99c2b225d7b179cc" - deposit_data_root: "0x7f90ce698e743d4711e8b9b3f6ab4461dc9f08f24f98e9a89319c3b0a526f37f" - eth1_data: - deposit_root: "0x53f5b42be17a65b15e69dedbb6555c8c16807de1e45febdf338ad2cca4172620" - deposit_count: "232" - block_hash: "0x7de5afd9e8b8fee40adb38fb6fae15c8bd6270fa7c2985eeab19ba6cba7f8084" - block_height: 233 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - deposit_root: "0x53f5b42be17a65b15e69dedbb6555c8c16807de1e45febdf338ad2cca4172620" - deposit_count: 232 - execution_block_hash: "0x7de5afd9e8b8fee40adb38fb6fae15c8bd6270fa7c2985eeab19ba6cba7f8084" - execution_block_height: 233 -- deposit_data: - pubkey: "0xa050c5180ee6cd9daef723caa6367112bc4b06636c3da59c3377e5e7b536942417a29cd5d1dad73011db4492032caff6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9208506ad4ad676f471b05fb33ab7d87b7b1fb7d4a8ef630c87407a568150aa88df852bde74fa50dbcaf3b9f81211c4c1759eed3952f470772d2dbe2c57ed35886530659cf3838ac540d9be98309549ff7121eb959d9f6deed0c25a63fb2a944" - deposit_data_root: "0xba747d4e778466e602ce18816cff1e7c36f7a8c16531a624a3920df6d7650441" - eth1_data: - deposit_root: "0x29f2b6c06fc3cfa95c9960525011a1ec1ea97bcb65ece736ac0714e6699a308e" - deposit_count: "233" - block_hash: "0x046a47e1afbda13b44861a64c6013262ce2d2bbd3afc4c0e72f55a049acc2acc" - block_height: 234 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - - "0xba747d4e778466e602ce18816cff1e7c36f7a8c16531a624a3920df6d7650441" - deposit_root: "0x29f2b6c06fc3cfa95c9960525011a1ec1ea97bcb65ece736ac0714e6699a308e" - deposit_count: 233 - execution_block_hash: "0x046a47e1afbda13b44861a64c6013262ce2d2bbd3afc4c0e72f55a049acc2acc" - execution_block_height: 234 -- deposit_data: - pubkey: "0x96dddc7430e5f035c0c4d005ef950eea0b3cb8188fa1db85b947bfa69745d4cc0f8ca522b3880823154796fcfedd970b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x88df1ad86744a29bc86506d348e9e3f1f762a1a266ddb6a655a34cc45ea7897cb5467d267022e8e8c9f280ca2aecec081652fc8ac2a3de171c6e240687fe0564bbb5671e46ee029cb3ae63b900838793b89913966a4823499fcc2be104c8b177" - deposit_data_root: "0x40f5c80a61e389915cd4d3e5f80ab4db55c85a2f4b2e57bf758f445538dcd272" - eth1_data: - deposit_root: "0xbfa95468dce6817d6393887438d0a90146367ced8533a2667f78b841e6c4a9d8" - deposit_count: "234" - block_hash: "0x28781e828d46a57b76c052c4b3ef44160cac67098a057a94faebc2f4e3540d74" - block_height: 235 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - - "0xaba4968db31e01e16a317caeaa2d3bc79f6efbe46926a9f8bf6576931d120e34" - deposit_root: "0xbfa95468dce6817d6393887438d0a90146367ced8533a2667f78b841e6c4a9d8" - deposit_count: 234 - execution_block_hash: "0x28781e828d46a57b76c052c4b3ef44160cac67098a057a94faebc2f4e3540d74" - execution_block_height: 235 -- deposit_data: - pubkey: "0xb1eef7970c10f8bb2a2d70f566657cc0209b198011ee3a4aaec9e7ddaf571fecc03db1042997b1554286e52ea95cce27" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa6274df964c6175a7c789c2244c212bebda8c658fc21f3cc9cc2aaa81294785c9f3985bdf9203709b4bf4b945d25e60e02cc06cc6d9a5a1b88785ab48488d820458986e9561c4e45ec3f5627573fb7e499ca98c5b9461448a1e8c78f77dbe104" - deposit_data_root: "0x3f6f7e82534853f2c9d372660aa30baf5c68749d16f7e01a4a208b2bb735d102" - eth1_data: - deposit_root: "0x000d046fe7ebb4a367257dd30536ddce1ba46896bf412d3b4315ff19c62a17cf" - deposit_count: "235" - block_hash: "0xde2f782bde063ebce8d17a6ffb690e59eef61c57854fae148a85d05a1d062c45" - block_height: 236 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - - "0xaba4968db31e01e16a317caeaa2d3bc79f6efbe46926a9f8bf6576931d120e34" - - "0x3f6f7e82534853f2c9d372660aa30baf5c68749d16f7e01a4a208b2bb735d102" - deposit_root: "0x000d046fe7ebb4a367257dd30536ddce1ba46896bf412d3b4315ff19c62a17cf" - deposit_count: 235 - execution_block_hash: "0xde2f782bde063ebce8d17a6ffb690e59eef61c57854fae148a85d05a1d062c45" - execution_block_height: 236 -- deposit_data: - pubkey: "0x85f65540cda46d23cf0ef948b5793b46a81771b4621ffb4b98c6dfa222208ffe3215bfdd436eed5ab124969704752e18" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x82b2f53b7b688fe92b7f3c0f991e2a8877c7da12806e596d2a36fe3d444a5e7fb2ab90b3989d2e0e7e3bd1af6086cc1a05281be5c581ac36e4a888f10460e6b1a5dc9f1de72580932df9a88fab6d0aa1646fd580dc54118bb08670569df173b0" - deposit_data_root: "0x14f05f69965b40e8362e9ccedea325ba0686bbe2777dcfa7db391cf2176e9dd7" - eth1_data: - deposit_root: "0xfc113b844286b5723c0470bacc323281edcb6c74aa79e716649bce8785ac4bbc" - deposit_count: "236" - block_hash: "0xad091a6f9838ac87857dc11a206927e15fe263591c391d3aee5ddf3ab5cd19f0" - block_height: 237 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - - "0x0f7262bce5861c90229709693935a8f977f19c7ea94b4e8bd1f12985baecf8bf" - deposit_root: "0xfc113b844286b5723c0470bacc323281edcb6c74aa79e716649bce8785ac4bbc" - deposit_count: 236 - execution_block_hash: "0xad091a6f9838ac87857dc11a206927e15fe263591c391d3aee5ddf3ab5cd19f0" - execution_block_height: 237 -- deposit_data: - pubkey: "0x8d016fd1936593cb41b3e94c01ae213a770cfcc1e94e06bf90dd77dfb8721090cd17255aeb4b4da53bc2a70257d2bc5c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xac1c8006b0200c3dd436b477bb2b280bb786f28790bf5da9fe4011ac3fadd996d6409642ec024fd238e46c1f3aa84a4716752ece5f09fe5f595a257c2eaa516375f9911255f91e938cf27ba02ee75abb5dcd22d2f25659d1212dc0bf5dc4b9d4" - deposit_data_root: "0x9cfa02a46e3cc3e3f5aad20774b3f64b1cb9808321b3604b55b87e0868c9bb95" - eth1_data: - deposit_root: "0xa18458316c22446c517019010eb193e2277fde1eb5aad56e1e42df9c231b2e31" - deposit_count: "237" - block_hash: "0x691970c03abbade8133576a1253209024f4bc0139674b807e6204bc3e23793a2" - block_height: 238 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - - "0x0f7262bce5861c90229709693935a8f977f19c7ea94b4e8bd1f12985baecf8bf" - - "0x9cfa02a46e3cc3e3f5aad20774b3f64b1cb9808321b3604b55b87e0868c9bb95" - deposit_root: "0xa18458316c22446c517019010eb193e2277fde1eb5aad56e1e42df9c231b2e31" - deposit_count: 237 - execution_block_hash: "0x691970c03abbade8133576a1253209024f4bc0139674b807e6204bc3e23793a2" - execution_block_height: 238 -- deposit_data: - pubkey: "0x953e4c8ef12a42ae5376ba23e06cfabf11c5e3fa773a98b487723225f1a2df60be50a54a7d5621bd85be25ddeb73af38" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x933a3fe4013d7669896335b2c866b160b31b1032bf990ffc350d94c747effc2c2e29f189f875246ef57dc6a0bbd143a200c2c48be3182476725fa3844a52f2a069ee18900e1c2b039d11bdfecb4ac068e3a1dd5c73b3d69db0f1b51b240c4f9e" - deposit_data_root: "0xcd3264430f4abf9cd490f8f5712b274a53c4a32c3333624f39ec75bae8fabe98" - eth1_data: - deposit_root: "0x9e37fd0fc2fd4db56f06797479f91b79ddead19653a7aa07c766429cea8bf45d" - deposit_count: "238" - block_hash: "0x866e5ee2515e964c4254798b6ad79900f96759665c738b22e7ece20d6e286b50" - block_height: 239 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - - "0x0f7262bce5861c90229709693935a8f977f19c7ea94b4e8bd1f12985baecf8bf" - - "0x9cf3b21fcd49adade498b802a1debdb7d2beec431c1aa4372eef06deec2bc955" - deposit_root: "0x9e37fd0fc2fd4db56f06797479f91b79ddead19653a7aa07c766429cea8bf45d" - deposit_count: 238 - execution_block_hash: "0x866e5ee2515e964c4254798b6ad79900f96759665c738b22e7ece20d6e286b50" - execution_block_height: 239 -- deposit_data: - pubkey: "0x90b3bf8ea91bae7c8d0e33974c65e1ce677328030cf63111d9ee5cbf6d300c72fc033c3c70d6afc21858fb9d2964875a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8462a3226ea043966c38f560258a0628da44dd4ca7daeaf82bbc64b77c21a5a34f2ded52a1ff0c012b6538e8e49d7801045b3705d346fcef4d6cbfdd694f784b4a557ebf6354c0e64554a31ccd8bb0be35c6aabbd8f0be2f02face2853211013" - deposit_data_root: "0xaeb3b2cc80454c6e05ea47a0511b3a569f488624dca56695d4f984414a0ab986" - eth1_data: - deposit_root: "0x3148e7f341bb5610dcaf8209f84e28569aa86f787a4992e1c530fa35ec25851b" - deposit_count: "239" - block_hash: "0x1eadc81c31cc7f4b5c446bd88dcba2fa748773b9c6f847b1f81b254a9db5cd29" - block_height: 240 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0xcfbab008757754f57710fd99c573283e4cbb498498c07b5f6d1a5c1102ee3bd5" - - "0x0f7262bce5861c90229709693935a8f977f19c7ea94b4e8bd1f12985baecf8bf" - - "0x9cf3b21fcd49adade498b802a1debdb7d2beec431c1aa4372eef06deec2bc955" - - "0xaeb3b2cc80454c6e05ea47a0511b3a569f488624dca56695d4f984414a0ab986" - deposit_root: "0x3148e7f341bb5610dcaf8209f84e28569aa86f787a4992e1c530fa35ec25851b" - deposit_count: 239 - execution_block_hash: "0x1eadc81c31cc7f4b5c446bd88dcba2fa748773b9c6f847b1f81b254a9db5cd29" - execution_block_height: 240 -- deposit_data: - pubkey: "0x9412476b39b4c36ba977e5aa1bda710a773b48c95a6486d0201a978eaf58c51bce7c8b37e34f3bd61322c2f7caf53bc5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8c5224f300841a22377468fa8aa7a8c5d1ddb596c6ec85c7139724cf83c6b0707ec61f9a02a1aa185e553c260835f860f599d2c4afb79bd5506e8f077dee4cedaee36cd6ecf67dc5183110b1d00cdcc6b2fc98a61d41acaf5c207330bb535d6" - deposit_data_root: "0x65bd202a5b6f083b0e57a1d1a1d99e2cd60a71071788a0ebf89723cf1f98430c" - eth1_data: - deposit_root: "0x8e9055eb4be6187b776047300d22266af54af5aa362e78bfe60b8815d30d06a3" - deposit_count: "240" - block_hash: "0x6f0584cc8761c8739652bd61a2e7c6f89e6a3e0c748b8555f6c900d566081bad" - block_height: 241 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - deposit_root: "0x8e9055eb4be6187b776047300d22266af54af5aa362e78bfe60b8815d30d06a3" - deposit_count: 240 - execution_block_hash: "0x6f0584cc8761c8739652bd61a2e7c6f89e6a3e0c748b8555f6c900d566081bad" - execution_block_height: 241 -- deposit_data: - pubkey: "0x8202a977e0d543f09f5f4b010fe308031db9c022058591b4ecc22205853f96fab8a906e31659cec3f20c23deced33fb0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x819cac5109e38a3e23be784a0803836ae5892a7ebf529d5b69ec3483f0765955d21453a7c8c9dbd48a67c6f6137484c9125a886a9562a70333f4a4643c2cf0a74899bd1b6f50501b25b73919a81b3a590e432af6d9d1649c484fd58d38a80446" - deposit_data_root: "0x3c87bafbdd6e4bd6fd9c9b911859197b0d4e77215d1f0b4a76cb53dfd9a0bee3" - eth1_data: - deposit_root: "0xfecc08150b61a8fd00bef193f76e8b08a35b1e230a8fbe5fe8386821042dc30e" - deposit_count: "241" - block_hash: "0xffd46f6cca04a453afaaab6759ce73a0883c43146bd59559c0e3547b03104bee" - block_height: 242 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x3c87bafbdd6e4bd6fd9c9b911859197b0d4e77215d1f0b4a76cb53dfd9a0bee3" - deposit_root: "0xfecc08150b61a8fd00bef193f76e8b08a35b1e230a8fbe5fe8386821042dc30e" - deposit_count: 241 - execution_block_hash: "0xffd46f6cca04a453afaaab6759ce73a0883c43146bd59559c0e3547b03104bee" - execution_block_height: 242 -- deposit_data: - pubkey: "0xa6071c891f8c353b0ed693c039c54f8ceaef1862c23904ecfbd88b3ca9180a52d7c4ce95bb9673e98ba6ac93a2d5b462" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8492293596dd1459367cc4ed401f6b3c2dab0c4c58045157a782a359483588f7fc072c8fab68311956fcecca8dca20ad0208bf6106be93330bb2c8f188924827efbf388ed1196f0a36c57751ba9241f9e888287f28c08f6f466f14ae39b7a471" - deposit_data_root: "0xfaf043c9b1a28720d15e0878bf714c21ff2f25732db766df36c30f2294edc890" - eth1_data: - deposit_root: "0x25d55cb11a780617ff862a96f6a5d28b03553d6ad721b3fd9b2a969516092366" - deposit_count: "242" - block_hash: "0xdc1ba0aeca3dc68376239c9dc4291a18b77e5a6e073c2ce216f78cf916b1bfc7" - block_height: 243 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x6ca839b6db690b690cfcecf7cc14423bb1fd7b32d682e21d2e2ac03494a37935" - deposit_root: "0x25d55cb11a780617ff862a96f6a5d28b03553d6ad721b3fd9b2a969516092366" - deposit_count: 242 - execution_block_hash: "0xdc1ba0aeca3dc68376239c9dc4291a18b77e5a6e073c2ce216f78cf916b1bfc7" - execution_block_height: 243 -- deposit_data: - pubkey: "0xb722d39c1d7d9c5ec39961f903efbf7bfd5faa1a3af41749874b52d9a6ecf554b022beebfd42b5d4bc00ecdc726a1c69" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8cd8f6b02f810e9b408185e8d25e0739f91a10d1091f0af98641b558598e8cb0f447f07dcaa702c40ffd3fd50e2fcb1e13fd81b3e6b5541d2c73facd4b1ee023578075d09356b8428e94cf257ebc725b94c13cf383ca033adb26f458477806f9" - deposit_data_root: "0x084781b3e9bd75b87361a1f8441b80da9923d7c8668720ad8e5755482ef29649" - eth1_data: - deposit_root: "0x5e30bf9c714431e60981eb475a64bf5f497cb20bee164b4c2c77fcf330d5cf1f" - deposit_count: "243" - block_hash: "0x650a223ced62d289d54524b7d13cf38a72a713b7d76a950da2127bb06567174f" - block_height: 244 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x6ca839b6db690b690cfcecf7cc14423bb1fd7b32d682e21d2e2ac03494a37935" - - "0x084781b3e9bd75b87361a1f8441b80da9923d7c8668720ad8e5755482ef29649" - deposit_root: "0x5e30bf9c714431e60981eb475a64bf5f497cb20bee164b4c2c77fcf330d5cf1f" - deposit_count: 243 - execution_block_hash: "0x650a223ced62d289d54524b7d13cf38a72a713b7d76a950da2127bb06567174f" - execution_block_height: 244 -- deposit_data: - pubkey: "0x98bed18337602301002525627e99e62911e6bc491e2c2bbfa2eef2f4bcf173eb60e493b74e95c3b5256555f20535694e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa30352b10445dccc53569ef6a776814dd6bd35bca75d4960a97108c2ef05e896fc1218863e091e349eafa964df4252cc04aad236601220b9a047fdd8b61cc370514e05c427f83215b19beef3672008bf441e58bc9e848f68617e580d8a3851c3" - deposit_data_root: "0x714f0a4796ecb56128e81e1fd76733314b4f8d9876254793390161eed47581ad" - eth1_data: - deposit_root: "0x58b247bf9a29620b5b3c182f0a579da72ad4126fe2ba57300f6fd58f32aba0e2" - deposit_count: "244" - block_hash: "0x84da630cce06cc7ab9def7b0e4ccb64c267d8d2be50f211f991cf5e4e4ce6e5f" - block_height: 245 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0xa46deb2a2056ceaf788d88266b62aab355e7755244c7ea45646a731284b588b7" - deposit_root: "0x58b247bf9a29620b5b3c182f0a579da72ad4126fe2ba57300f6fd58f32aba0e2" - deposit_count: 244 - execution_block_hash: "0x84da630cce06cc7ab9def7b0e4ccb64c267d8d2be50f211f991cf5e4e4ce6e5f" - execution_block_height: 245 -- deposit_data: - pubkey: "0xb499f66668919d5f1d82e55171c5961e78aaa70e0fab3a2ccf14aa402379ac66e05d542060e550d8a1d03796ce703a3c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb52925ffbd2205903de239cc9abdfba52c6940f45cfc9f8c0d6998c074022c32fefd229d464724fb372a157a0c74bc87015c05f9b331b96b9354a9060a31cb1a92ceab4e91a689e5aa4156c8dbec1785a319cd74d370a3bf86aa56a431033d66" - deposit_data_root: "0xdab69f68d68d19729a7255e07d59760c7b6ba098132c4d5d5a1c87af8e5ec222" - eth1_data: - deposit_root: "0xf1bd510bc2186b7e3cf6246ba45b1a8be2ec957093caa0f825d8d2ee898b11f7" - deposit_count: "245" - block_hash: "0x958e63436b683ab4d02c33db4495fcd0dfd5a72b445e3274b9efc508d4d797c7" - block_height: 246 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0xa46deb2a2056ceaf788d88266b62aab355e7755244c7ea45646a731284b588b7" - - "0xdab69f68d68d19729a7255e07d59760c7b6ba098132c4d5d5a1c87af8e5ec222" - deposit_root: "0xf1bd510bc2186b7e3cf6246ba45b1a8be2ec957093caa0f825d8d2ee898b11f7" - deposit_count: 245 - execution_block_hash: "0x958e63436b683ab4d02c33db4495fcd0dfd5a72b445e3274b9efc508d4d797c7" - execution_block_height: 246 -- deposit_data: - pubkey: "0x92c237d7fb491bcf70aa4e50e71b305bcf71bf5a438b0e08f03fc4f74d5c3c9b8c823049e314d15f8266179b1a90841d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa2bb7c6a1ee2ec8368539cccfb5b2a8588e5e479fda223410cd15341dd5c865dd22695a2a0ef27757b2545adf353df0e0b51f9c9c12b19a7fd0e22cacc81c28a0fad805cd19feb808be5ca4867eabee207e2f38e089b6097f66809b04cd41797" - deposit_data_root: "0x1cc0cbc5cf5550036124427fca2b43440d4c574e10da612c1b6c583a6febe147" - eth1_data: - deposit_root: "0x4ba339c19953b83d57a0600cc2a325b4ab96d46eecbd4b6b483d42b6c3035122" - deposit_count: "246" - block_hash: "0x4194ca1e0c55ccaa492a55535bebacaa185efcc8ab9f5fb8ea60ffd90461d3c2" - block_height: 247 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0xa46deb2a2056ceaf788d88266b62aab355e7755244c7ea45646a731284b588b7" - - "0xfe1c7788bf7f661ff91503ec1352ef2ba4d8ad3653fe69110da96218c90b32ab" - deposit_root: "0x4ba339c19953b83d57a0600cc2a325b4ab96d46eecbd4b6b483d42b6c3035122" - deposit_count: 246 - execution_block_hash: "0x4194ca1e0c55ccaa492a55535bebacaa185efcc8ab9f5fb8ea60ffd90461d3c2" - execution_block_height: 247 -- deposit_data: - pubkey: "0xa1f2eb8596281b7e85285bcee8a4156140e069c6f50c02a0dd3b0f40ed62b8e15e7b9c40f6755496e13a94d91faaa194" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa3b72e3ec51a30a080e36fb0ad1c350a6f4df753de841d2626581c2f3573b3ceab177708cfe9a64d3c6d243a26df1e6700b014c3f09636bd0911addd3db4be6b8893568c0e1e92b88badec7a7c86e0c0921cb07ad48e3c7f6c27a6c6cb1ea8af" - deposit_data_root: "0x571c10a7c40e7c4a5f15db4ff249f1e1b90fc1ed392d6dc2c359de9c887950f9" - eth1_data: - deposit_root: "0xc8f68085a6d6b9ea9ab9389235e301c66d605c6a1648e659e512d10a340f6dad" - deposit_count: "247" - block_hash: "0x86ca50c675d6f26df298aec00b900a9693d04a765494714b8a2c6da02d310338" - block_height: 248 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0xa46deb2a2056ceaf788d88266b62aab355e7755244c7ea45646a731284b588b7" - - "0xfe1c7788bf7f661ff91503ec1352ef2ba4d8ad3653fe69110da96218c90b32ab" - - "0x571c10a7c40e7c4a5f15db4ff249f1e1b90fc1ed392d6dc2c359de9c887950f9" - deposit_root: "0xc8f68085a6d6b9ea9ab9389235e301c66d605c6a1648e659e512d10a340f6dad" - deposit_count: 247 - execution_block_hash: "0x86ca50c675d6f26df298aec00b900a9693d04a765494714b8a2c6da02d310338" - execution_block_height: 248 -- deposit_data: - pubkey: "0x8f4d945fafc936414122945190a1983192259705205fa3e92b72443a93183eb140ad527d4b1449507a18cd64764c89b8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89f9fb93f365aa814e9939d47d12e479a941b124428a3ea45ff751b336fbfc4e918be34e1f8e1006f011a1182f3fa4530ffd887727627586a9d090ba05c5f035a69fe7da7f3747d9018eff61f281ebcef266db93e508b22273173d7e59337234" - deposit_data_root: "0xd752ab6ab4c095111b65b97cef47817f4ce89cf1025672ca33053c1c5cc8e856" - eth1_data: - deposit_root: "0x9888c051a7c8320f413b98a66f0f4d86d2161c649550a898f3a05d547072e2ef" - deposit_count: "248" - block_hash: "0xab019baaf509e52a456129c7b9628360234c5c99d73059d5f3a6f1c320affeec" - block_height: 249 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - deposit_root: "0x9888c051a7c8320f413b98a66f0f4d86d2161c649550a898f3a05d547072e2ef" - deposit_count: 248 - execution_block_hash: "0xab019baaf509e52a456129c7b9628360234c5c99d73059d5f3a6f1c320affeec" - execution_block_height: 249 -- deposit_data: - pubkey: "0x98a8e7af1994e3d8f383dc4ee6d32a01077694ec93ecfd7ffbc5cd9448938a6c6d35cbbbe8e01a49fa4782f389757b06" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96b654ae3c1bbef1c62c4dbb5ff4a174dd66d699660b3fddb0393dc3f8b675611a8f7c92c9d64a55c70c59c81ec61d9c0630b6609ee9185b075931251f5bfc63be9b5741cd1bff31d08bf63232ea95a3652ddf2012932c617741afb9417c1797" - deposit_data_root: "0x1b3ff046f35984553c7bc5e78b4ef9633fbd4955e81438f836224ad1a35537fd" - eth1_data: - deposit_root: "0xa1477aaa9d366844c18a57e9c0f5281dbc35d8a27c7178e36a78943f2af1f4ce" - deposit_count: "249" - block_hash: "0xefe08745ea4a98e988389f4896dec09001efcd92fde1b157958b2a7b5bea1e1f" - block_height: 250 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - - "0x1b3ff046f35984553c7bc5e78b4ef9633fbd4955e81438f836224ad1a35537fd" - deposit_root: "0xa1477aaa9d366844c18a57e9c0f5281dbc35d8a27c7178e36a78943f2af1f4ce" - deposit_count: 249 - execution_block_hash: "0xefe08745ea4a98e988389f4896dec09001efcd92fde1b157958b2a7b5bea1e1f" - execution_block_height: 250 -- deposit_data: - pubkey: "0xa7c051843950c482ea373ca3aa30e523911ccfe061c681b16fb8a35ec6e4ff0ada5a25fb2ae70433b18913652cc3c181" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x98eebdf73162c70581f7325614bb7110c6cba9351ca1dd4e275557a9cf71ac8fcd64071791780806cee2e37c18f98e2313e8adfec420bfbf20b35a79cfad2eddcd664a557b3b566437f567f1a5a03deeb48b0c0ce31ba57531f594ca8317fe2f" - deposit_data_root: "0x357d15b4cce193ddfc397cdd46ab0a749f8a579ab81cd19f1100e041b1147c57" - eth1_data: - deposit_root: "0x11c2be42ec7670fab1675225953d5079ce0571d96393b911904526f6be98cf25" - deposit_count: "250" - block_hash: "0x65b114ab411f03138a6610b8edf979ba983e536afb438e62fb80ffffbc3cf124" - block_height: 251 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - - "0xcf504f082f480bc2de32cc4c299b0fd96b1ed6919af3b090f0a9f3f3ecc10430" - deposit_root: "0x11c2be42ec7670fab1675225953d5079ce0571d96393b911904526f6be98cf25" - deposit_count: 250 - execution_block_hash: "0x65b114ab411f03138a6610b8edf979ba983e536afb438e62fb80ffffbc3cf124" - execution_block_height: 251 -- deposit_data: - pubkey: "0x8f14a40f502a87214891c922345fc2abc6795c28d72f9deb7ebffd7b805888222f7b1d479db1142ed013902987394cad" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x85f4239f73421ff5d28f31481729632e20bf4ed4b3ea0a91961dec198922dc1be61be0623b85365dd6db9cec094dad9217683da190c4be92b5d1bb00491314472e3a412f13d651c4c98f054219e621538c9465cd42fd2851950e0ac9a42e62b0" - deposit_data_root: "0x5be2cc49961bb2f24b8e89e5e9bacc33efb9cb9f9a455e1bb91fff7bc5b6656a" - eth1_data: - deposit_root: "0x0df7d0d38d74a5cb89be95258e7c60ce0b9200342553536b064f33ea923255df" - deposit_count: "251" - block_hash: "0xb243d9ace275e844a2335d262010f8b3cf19d045bc930d89bb6e6ec738865cb6" - block_height: 252 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - - "0xcf504f082f480bc2de32cc4c299b0fd96b1ed6919af3b090f0a9f3f3ecc10430" - - "0x5be2cc49961bb2f24b8e89e5e9bacc33efb9cb9f9a455e1bb91fff7bc5b6656a" - deposit_root: "0x0df7d0d38d74a5cb89be95258e7c60ce0b9200342553536b064f33ea923255df" - deposit_count: 251 - execution_block_hash: "0xb243d9ace275e844a2335d262010f8b3cf19d045bc930d89bb6e6ec738865cb6" - execution_block_height: 252 -- deposit_data: - pubkey: "0x8e486d8c9ca07b6c4dc1181161ab52508b70980e9af31c97286b55be3df3701d73891bf0aecc6eb7d2e028572bb5c25a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa87d84736b8a674718e17bcabfd512e8a6ccb52eaf11776e2aaec8cb0d9bf91fa59b46b3c21a0023443a9911ba1f6e9f192e18b88ea8cc96362a537f98bac4de9883e4a8b46ce99f8c5407b4fa748a9bd6c438ea076b8eff66a34c43243f1030" - deposit_data_root: "0x94423ecf41a112308585b194e910a68273a90cb12434e82d0cd936a3b92309c9" - eth1_data: - deposit_root: "0xabdddf0fbff2f71cace266b85d33a05b0621daae752c693fe9027725d863a1b8" - deposit_count: "252" - block_hash: "0x8c746ea8ee851c0ce8552221531374c1a73ca48d07c2da85ad95328836cde732" - block_height: 253 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - - "0x5ba7e2c0ce36932281840d77f2cff6b2fc9b10b24953346e039e8d0576ba01d8" - deposit_root: "0xabdddf0fbff2f71cace266b85d33a05b0621daae752c693fe9027725d863a1b8" - deposit_count: 252 - execution_block_hash: "0x8c746ea8ee851c0ce8552221531374c1a73ca48d07c2da85ad95328836cde732" - execution_block_height: 253 -- deposit_data: - pubkey: "0x91402ee72dd351a735017f11da3176ae5fce3e92369972d0eee22a45d1cc2badd77f627c4a0a9d9c480d65464442599d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa1eb19f9f0c9fe4962b82a001fe8c99c3ecf7b2133202c655275af9ba4581d7715abafb9d9835340a14c52ee7a1a449c0ba1d35ad7bb6ab37fe54d05c644a577c5b86ef3e036c46c0c4ccb3829205e0b4c717d2392abf2ac92e770747643c26a" - deposit_data_root: "0x00c52950b4b8050d147aeca91703761b297e5d91ab496ac1161f1fef79f7528e" - eth1_data: - deposit_root: "0x39b19f70b06e4cdaa2c3e1c87ef0b8039022ac99b00d68deca929ae4f7ba95f6" - deposit_count: "253" - block_hash: "0xc0d6735edb092ab85379457c91496c754307a70cab6e50494f858ee371eb649b" - block_height: 254 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - - "0x5ba7e2c0ce36932281840d77f2cff6b2fc9b10b24953346e039e8d0576ba01d8" - - "0x00c52950b4b8050d147aeca91703761b297e5d91ab496ac1161f1fef79f7528e" - deposit_root: "0x39b19f70b06e4cdaa2c3e1c87ef0b8039022ac99b00d68deca929ae4f7ba95f6" - deposit_count: 253 - execution_block_hash: "0xc0d6735edb092ab85379457c91496c754307a70cab6e50494f858ee371eb649b" - execution_block_height: 254 -- deposit_data: - pubkey: "0xaa5b90007d36de7c57235756d39aac060ee48e8a910f0a0c815e71414a0d53e7b0659242079253fdeb32ad18ab90beb9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x85e04b0dd29327c3b6509673add484f5236e2fc68e0d3411f7ac5070b5d171b8800cf366a8cc632d9e9b9ea49bad527f0105dc4600d55dff610464d15d70c1103218824db85b2c13a898279e777f843cb3c32afea2ab7dd46494c942fc857da2" - deposit_data_root: "0xeec4762f004c245237202597a1172ba2594a24318eab15dc61519909d761035d" - eth1_data: - deposit_root: "0xcb2db903b62421b1add0e3747dff29b0eaa2046f7e8aea0c22777804c1931533" - deposit_count: "254" - block_hash: "0xb3df67d8050a51998dde8c9c9a2ebb92c608018a2c32b1bb4835769bc7303160" - block_height: 255 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - - "0x5ba7e2c0ce36932281840d77f2cff6b2fc9b10b24953346e039e8d0576ba01d8" - - "0xc9bd7dd686e17ced16ded792dbb1565f93d9d01b2e551b237d8674891756d761" - deposit_root: "0xcb2db903b62421b1add0e3747dff29b0eaa2046f7e8aea0c22777804c1931533" - deposit_count: 254 - execution_block_hash: "0xb3df67d8050a51998dde8c9c9a2ebb92c608018a2c32b1bb4835769bc7303160" - execution_block_height: 255 -- deposit_data: - pubkey: "0x851faec0a50c3df9918d1ab44fe7f63f8105d671d8fb83f3a59d291e6d4dc86cf47a6e9402973bfbd7db535b3dc4350b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xabf53104df47484b8c876216dec49ae4c6805c0c7c866fb3794f0699db316ef4e60ed588cd3a2865217db953a640fa61118325f454eb4d2768f2372bc8325b5a979a722faee2bd818d9bf944c89abbefc3328441ecdcbcab2a9f0830bad57a3b" - deposit_data_root: "0x3fe14e2e6126bae3261e24ff9119ea5b74664a19426da0d091193ae3bb736dd5" - eth1_data: - deposit_root: "0xad9929ce3da3cccb228d8f69211d8493147a6df93daed02e410bcbdecbeaca60" - deposit_count: "255" - block_hash: "0xff6935719c772b51352c991341ff30606fee102d3dfd07859a2930212e75800e" - block_height: 256 - snapshot: - finalized: - - "0x5424209b0511223b880af7362d8fe72bc0f268f4aa37dbcced5b872a30a1265e" - - "0xbbe623353e1418ef9492c6e1ca72e9a73bc4d28b00d17bae94476c70ae774043" - - "0x8b839672af192e30144c2c46a2d14842b4620d58405e9094689262016f3b1ff7" - - "0x15e5ab79d2e91316abac7981530e445f4c70332f1628d1883a21d17a699c1b4e" - - "0x151d5a637c3241cca69bf3658d39f77db74646866a4dc1a1d762fbe89eb8f459" - - "0x5ba7e2c0ce36932281840d77f2cff6b2fc9b10b24953346e039e8d0576ba01d8" - - "0xc9bd7dd686e17ced16ded792dbb1565f93d9d01b2e551b237d8674891756d761" - - "0x3fe14e2e6126bae3261e24ff9119ea5b74664a19426da0d091193ae3bb736dd5" - deposit_root: "0xad9929ce3da3cccb228d8f69211d8493147a6df93daed02e410bcbdecbeaca60" - deposit_count: 255 - execution_block_hash: "0xff6935719c772b51352c991341ff30606fee102d3dfd07859a2930212e75800e" - execution_block_height: 256 -- deposit_data: - pubkey: "0x933dc2f8d407d4f85a1d54953ac47a89c19808eca0d38b0ec85b376ce17c3ef1edebb518a4dce8e976155cb6561a849f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9694a8b0268e018870eb146f9d2a4dda7b9c341b4c82a73d950101d534f167fc0c8d902f37f9589c056108b1b6a3d41a0d815100b4db1232c4393e5f7bdbb23925aa043d68f95ba7b4edd2d4e58465ec63aadc831f99eb06278e067fc68325c5" - deposit_data_root: "0xb71c9023acf8ceebefff85ec63ced2d0a535e3af465941895e0679b0fc692f0a" - eth1_data: - deposit_root: "0xd49c9ff7d98a8d5cdab9ddf57c558dc9d203d282d7ec7faa5c7d11aae81b790f" - deposit_count: "256" - block_hash: "0x1fe7203f9539f6116aabb762433e3ec9692de44bdb1342b1b1653afc271b04b5" - block_height: 257 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - deposit_root: "0xd49c9ff7d98a8d5cdab9ddf57c558dc9d203d282d7ec7faa5c7d11aae81b790f" - deposit_count: 256 - execution_block_hash: "0x1fe7203f9539f6116aabb762433e3ec9692de44bdb1342b1b1653afc271b04b5" - execution_block_height: 257 -- deposit_data: - pubkey: "0x8f7069f51912347df8b721903bd9340ff49e9d436239ea0f4f176e360c4a91aede7657037ae00dcb08a9d7abe9b48d5a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x833313916a5da51c5ba85ecb314c687384faf62db7872fd000ca32a0ee7f4778bb2e5f7a5f4139aac884a7f4a0a1c40808a766bd7acb02c5ee4d6ead05a622b6d20a818649ed08c41afe600e1710f1b5532aee603618c828ee50cc62dc51f964" - deposit_data_root: "0xb7292e597a4316db41b1ee81280941feb78554bd77861d9b4e4ee85ceb83f536" - eth1_data: - deposit_root: "0xd254aeef26718bb674401e75886471ce4a088120fab90318c6e55d063aa04a3c" - deposit_count: "257" - block_hash: "0x1df4e3eafb6484a050b5168586d648e4191d90ecdf846800fd0b41ddde11edbc" - block_height: 258 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xb7292e597a4316db41b1ee81280941feb78554bd77861d9b4e4ee85ceb83f536" - deposit_root: "0xd254aeef26718bb674401e75886471ce4a088120fab90318c6e55d063aa04a3c" - deposit_count: 257 - execution_block_hash: "0x1df4e3eafb6484a050b5168586d648e4191d90ecdf846800fd0b41ddde11edbc" - execution_block_height: 258 -- deposit_data: - pubkey: "0xb876db47bf1d1868b3a39cdba4171adde47eae91b3631dcb5c59d28cb100ecda604a446ffcb000448c462d72526ebdf1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb7302e880887ee702f55cc3fbfa2a8ec3b422317eda123538723588e7119d6111d80457a3805912bd4f07f16cb71c32c09a2bf0ade7ca11fa2303136bcd493c8b8a7c7fff3b94be38684b019cb515d4759574323015b9d53d0c92ce374a5311d" - deposit_data_root: "0x40f31b70761cdda3e491f945fcfada69d0d985503e705ff9e0db0fde1d58c7fc" - eth1_data: - deposit_root: "0xd5c029480bf22b310991b6964481ff2e3e156e9d9a39556026ef809cf282d77c" - deposit_count: "258" - block_hash: "0xbc87b56c9955290b90555dfdfcfe36d95abf388b957b8b004ab7aa8682a48c26" - block_height: 259 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x33caef79ba14b442d517fa8bc4c617b1a5c595ab103f2baff90a65a7f03c8564" - deposit_root: "0xd5c029480bf22b310991b6964481ff2e3e156e9d9a39556026ef809cf282d77c" - deposit_count: 258 - execution_block_hash: "0xbc87b56c9955290b90555dfdfcfe36d95abf388b957b8b004ab7aa8682a48c26" - execution_block_height: 259 -- deposit_data: - pubkey: "0x9666598b3eaade229b7b255866d279009ac0b42dc55cbec207f3842d51501e2fcd318ce7d2fc40a766382703c8a0ef00" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x960b98ed5f4aec48beaf813e61048411a345de0d5b99593d5017f7a18651567b7d3038e4665fd186c868a4b4b8da865f175ef0158fba21bdf10c0890cd1fa32dc3105da9f295925b96d090516bace0c6e9a168206a21a11b30f8fefce22fbb5d" - deposit_data_root: "0xc0a4d26a15ce052fbb2873df1b178b41192e4cdeb45895acf94f55d5d58636c9" - eth1_data: - deposit_root: "0xda795c832687a92b18a304a4a68190b021082edac866a12e283b6cc4c3eee27e" - deposit_count: "259" - block_hash: "0x2ec63ba8ef1e86b8428bb223e6aaed2e3193f59a65de1ddaaeb3cbd96653530d" - block_height: 260 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x33caef79ba14b442d517fa8bc4c617b1a5c595ab103f2baff90a65a7f03c8564" - - "0xc0a4d26a15ce052fbb2873df1b178b41192e4cdeb45895acf94f55d5d58636c9" - deposit_root: "0xda795c832687a92b18a304a4a68190b021082edac866a12e283b6cc4c3eee27e" - deposit_count: 259 - execution_block_hash: "0x2ec63ba8ef1e86b8428bb223e6aaed2e3193f59a65de1ddaaeb3cbd96653530d" - execution_block_height: 260 -- deposit_data: - pubkey: "0xb2b4c1b1777970826b6683ceed5b72da7bec1f6f7cdfae6a599ae0d0d6d912098678327ee31fe383fc2b95bfed48bfce" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa0495982b6c281a33c67883658f6c32a94268ede93853331bae3e492f71ac4514f05b0e89ffddb1e1a8dff4b83dc807219c69ac11d5a2515c845023c566dca7dbcde3ca08855dae961242680efe3f84de27fd04ee3d8c580bacd625ce1618f7a" - deposit_data_root: "0x6944628944cbac12207257df3a7d14936c9bc8f267eec1e24d18543b00b9c413" - eth1_data: - deposit_root: "0x7db45a47b3b9ef4cf211c695fe5682e6e79078b72f4b758d7db497ef4f3c7abd" - deposit_count: "260" - block_hash: "0x2d162a08fa97029565b3f57c18af38429a5a4a3f7deefde7b843f481fb7fd0a5" - block_height: 261 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xe4b758cad7a082639814a7aae047e94921d416986d98b6dfcbc234966bc973ef" - deposit_root: "0x7db45a47b3b9ef4cf211c695fe5682e6e79078b72f4b758d7db497ef4f3c7abd" - deposit_count: 260 - execution_block_hash: "0x2d162a08fa97029565b3f57c18af38429a5a4a3f7deefde7b843f481fb7fd0a5" - execution_block_height: 261 -- deposit_data: - pubkey: "0x91cecc34498496a68dc7630a6184b1cd7b977772a94d7d704c64284cdc14aa3f4d7c6eadb3bf62eda1a43f2f8d547a2d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa2e8b32322fbe08e071a3100c7f55efa12fedb60e61d10adf35599ad6dff551e7cb22f16882c76e5a5fc03cfbb7cac2916770030f9049ab5075e8371745bfeb58d944bb0cfd9ec44e29e57ab441f96fad3b71a42ed1a3f7a8f13107a5e09ca8f" - deposit_data_root: "0x709db664502b7eb538adc5890972def651e1e2c0f4fed2b14b473022f3676bf5" - eth1_data: - deposit_root: "0x36baa65b71d95dada8a8da1cb67eb6656fa616b6645db7870b07060afdfcbdb4" - deposit_count: "261" - block_hash: "0x771faf677259330b1949d0d57781f6ae7745117adb1f2c748b42ba20ec3c599d" - block_height: 262 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xe4b758cad7a082639814a7aae047e94921d416986d98b6dfcbc234966bc973ef" - - "0x709db664502b7eb538adc5890972def651e1e2c0f4fed2b14b473022f3676bf5" - deposit_root: "0x36baa65b71d95dada8a8da1cb67eb6656fa616b6645db7870b07060afdfcbdb4" - deposit_count: 261 - execution_block_hash: "0x771faf677259330b1949d0d57781f6ae7745117adb1f2c748b42ba20ec3c599d" - execution_block_height: 262 -- deposit_data: - pubkey: "0x89dc8480e9d48c7e5a39c3ced17e65170f56f86a96e4bb131e88c3d6eda3daa65265df5445e0ce52090cf34d7fd1116e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x945d9ac700cadcbad052484acb2eb9702915bbcfa44222866ece07891edd00d8ce873f17704412e338f39893af82ce54115bd1e184b11cdfa51937164a1d3576f359c14a903ef669a016cc5398e9e5c6af03730ea0534a737f8457c237141f36" - deposit_data_root: "0x70c4d026dc4ce6566e02ddb3085517df6d5c742910fe7a36500d76e7badf82b3" - eth1_data: - deposit_root: "0x05fb7d7e57be47dd87666d6198c4e197ae9fd0922fb4100535592ac7e0900bab" - deposit_count: "262" - block_hash: "0x132a1b9db503b80713fc5a34717cd51069fa6f145340ffee8c0b283c8c2e0606" - block_height: 263 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xe4b758cad7a082639814a7aae047e94921d416986d98b6dfcbc234966bc973ef" - - "0x6cf83a85c4c666e9efb82ff97d8cf8fd2d6f4442b0c6c7e029dcf8ae80c981df" - deposit_root: "0x05fb7d7e57be47dd87666d6198c4e197ae9fd0922fb4100535592ac7e0900bab" - deposit_count: 262 - execution_block_hash: "0x132a1b9db503b80713fc5a34717cd51069fa6f145340ffee8c0b283c8c2e0606" - execution_block_height: 263 -- deposit_data: - pubkey: "0x8219db7b86441850836ae4e27a030e8378e594e5f1d7ee08dac7bc054653d178e6949476887c83a20a213b2bf39e16f7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c53a5ea1061f892c20cf93c1b97c42250908b3e299700090713f36a45421c619b06570d18e2b96168e71dcaafb820bd17331ed302480153fb48a685d679c829ea12e7c93ee5999aa0812ba19da8b8c60899b980401fb17312ae9db58046ae20" - deposit_data_root: "0x819ccfe405e7220a1d9c053a8676a88332980aef5215f5076f1a3f5033235532" - eth1_data: - deposit_root: "0xcca4cb084ab65bb8629ae4dcc8b2fcbe56db2cae8859e206c4da24a4014d139a" - deposit_count: "263" - block_hash: "0x27aa5166ec5f9d13e6be7009abc9114701756eb96a5a9110ca40a3e593b0fc27" - block_height: 264 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xe4b758cad7a082639814a7aae047e94921d416986d98b6dfcbc234966bc973ef" - - "0x6cf83a85c4c666e9efb82ff97d8cf8fd2d6f4442b0c6c7e029dcf8ae80c981df" - - "0x819ccfe405e7220a1d9c053a8676a88332980aef5215f5076f1a3f5033235532" - deposit_root: "0xcca4cb084ab65bb8629ae4dcc8b2fcbe56db2cae8859e206c4da24a4014d139a" - deposit_count: 263 - execution_block_hash: "0x27aa5166ec5f9d13e6be7009abc9114701756eb96a5a9110ca40a3e593b0fc27" - execution_block_height: 264 -- deposit_data: - pubkey: "0xb9ea48794c02725e69d9f6435382b31b3e975de5ead999a09e13746e0ae5a63115af3a3043cd71ebc94c9b051b5b595e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8964f0aaa92e1cd2a2e0df07b02766725afe98d100e863fa5364feaaec05c4159eb6efd5d9409d47ab037a5fdc41ba5300ddd8f1626f81b384a6a3793a70a5bcc8c4361c19540bf923cf6f2f5058832f8c188fbff56be3141d24fb9abd829561" - deposit_data_root: "0xca07d9e3d7bab36f22f9362f73c6302b8c8fc9c41ee856fb2c0fde28702d6672" - eth1_data: - deposit_root: "0xf051452baf67b48585468b8af81fea65efe163d4752bc57fe502dedaebc9595a" - deposit_count: "264" - block_hash: "0x7c9c165d2d0ad76293cb31edc3195d7de80f5cff9d5ba5bbd9bf0f384dff028e" - block_height: 265 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - deposit_root: "0xf051452baf67b48585468b8af81fea65efe163d4752bc57fe502dedaebc9595a" - deposit_count: 264 - execution_block_hash: "0x7c9c165d2d0ad76293cb31edc3195d7de80f5cff9d5ba5bbd9bf0f384dff028e" - execution_block_height: 265 -- deposit_data: - pubkey: "0xadae379b50e15e5f80810abfc544eb28e26301438cfaf8a8a5569f9bfd76c4e653d0da710bb5d34687d47669f6999257" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa56c12741c2b28f868708a31c9cde178d724fc2da24acc2538f54068937fb2d7e76cd4bef53818af4ab71908f3feeb0d17527506fed49643fab936ee040061ec66bd43ae9dc1e414d9ff03f7392a9d26daadcc789117c2e66596c2235fd98f8b" - deposit_data_root: "0x6449a8db47ed59b97b28d2cdc174f12232041fc3231f388d48cc5ed141ccd2a7" - eth1_data: - deposit_root: "0xe077d704ae5725c0d46ff39053c1ae7e15b456333bf520cefdef620ac331c134" - deposit_count: "265" - block_hash: "0xeb71722b0fe110a64ecccdb9ecba95d7fb7b7c832086cd5bc67e25196be9232f" - block_height: 266 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - - "0x6449a8db47ed59b97b28d2cdc174f12232041fc3231f388d48cc5ed141ccd2a7" - deposit_root: "0xe077d704ae5725c0d46ff39053c1ae7e15b456333bf520cefdef620ac331c134" - deposit_count: 265 - execution_block_hash: "0xeb71722b0fe110a64ecccdb9ecba95d7fb7b7c832086cd5bc67e25196be9232f" - execution_block_height: 266 -- deposit_data: - pubkey: "0xb451fc2c4236ca2393853980019f5da0e5f7d93df90bab1ae34ddb583d0ce3cdece25f23bc1736e065a1f2af1ee2bd34" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x93bcb192ee34cf2cb34310583c6ed82b12baa1388f5e85d3e74ef7c9dff413f69f00eff132a686eb786f33bd918fc8870ee0337447202ff82024b20ac35bcd931c34869d33f9e594da910891722146b65e3858be3f6142d1ddafcff50f159b95" - deposit_data_root: "0xf6e781e7f4285fb05464464c41580de16dcd370ae346a5462bb5791d38808f89" - eth1_data: - deposit_root: "0x91a3cef7fd152d3aab06e1f156a428c7f3b19697a7bf32bcbf076c37b4379230" - deposit_count: "266" - block_hash: "0xb87c760b0c73bb51052f5a77555e27d38ec417e21af5851664153cc71ea1b7f2" - block_height: 267 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - - "0x48170ce125065a69a3020c11a7f1aa34ecfaf772a3a13be6737bcba5157f2553" - deposit_root: "0x91a3cef7fd152d3aab06e1f156a428c7f3b19697a7bf32bcbf076c37b4379230" - deposit_count: 266 - execution_block_hash: "0xb87c760b0c73bb51052f5a77555e27d38ec417e21af5851664153cc71ea1b7f2" - execution_block_height: 267 -- deposit_data: - pubkey: "0xb8c6b853d7f3766c881f4eb0c9986b800188b9f9ab40a492d49e64e8c1e98cefc27ecfe225ff9d5781c0193bca2f77e3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xab4f31e234441c6eb8fc1f41628ff056226de0b0b3016bb48e2d02eb41ae39f7774bf6c6bf7f8e5088504d962e2045fb0acae329fb2e80eea51f332b76cb2f1d08339b6c230c6ca47b4837f5ebed0c087b51c0c29154e5c4e0b587a0a304cb03" - deposit_data_root: "0x1e826f811c943eec59894f7b90ff11deefdcc1449ee3dd9186a01e14e07f6c15" - eth1_data: - deposit_root: "0x480831312704cf1ebb18ce99d3d5d14aee83c31dee4a7768dc84b8f44a8f815e" - deposit_count: "267" - block_hash: "0xf65f73d8ffe1df07acfa02b5297f43c4652c159a3cfcdd3bb9f3f1bac7d7ce98" - block_height: 268 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - - "0x48170ce125065a69a3020c11a7f1aa34ecfaf772a3a13be6737bcba5157f2553" - - "0x1e826f811c943eec59894f7b90ff11deefdcc1449ee3dd9186a01e14e07f6c15" - deposit_root: "0x480831312704cf1ebb18ce99d3d5d14aee83c31dee4a7768dc84b8f44a8f815e" - deposit_count: 267 - execution_block_hash: "0xf65f73d8ffe1df07acfa02b5297f43c4652c159a3cfcdd3bb9f3f1bac7d7ce98" - execution_block_height: 268 -- deposit_data: - pubkey: "0x923b10adafbd70ac83cfde90a85bd38e5e919348285b311e020721bc96adc9e9b7e45f8052d1b3ef97301947cbc6d3e9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb8dbe590fe70f90ffcb0e4d7156fb9a763e0a5812a18fb900eb224dbfc1006ea53fe2995beeb8c68de87186093102daa072044fa2c7599d0ddff864035c094435b4bd4159157b8c5c2bb68b713313c33daed3f059742bf74bf9ee140996a8030" - deposit_data_root: "0xb230a394c34aa022260b632113aac600d6adda8c634e99c4a0ca133d01ae895c" - eth1_data: - deposit_root: "0xa94dd2d608ad39920b3d4d36983bcf3fcbf847bfba0fd6efb0f9392538d9508b" - deposit_count: "268" - block_hash: "0xf02da506c532e1e1fd10587045c93575c15526820e5ceacab2119c401979948d" - block_height: 269 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - - "0xe199368557ff186641e9a7d5cb05acac6d482b97df62a1011f0b9fd5c03f5734" - deposit_root: "0xa94dd2d608ad39920b3d4d36983bcf3fcbf847bfba0fd6efb0f9392538d9508b" - deposit_count: 268 - execution_block_hash: "0xf02da506c532e1e1fd10587045c93575c15526820e5ceacab2119c401979948d" - execution_block_height: 269 -- deposit_data: - pubkey: "0x8860a33d75543d49ad04bdcffdfdf7fcef7228076d4e8150d80c3cda14651963544a683355e256166d995dd3f6b4ad35" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x986c60f5e748e02284435fdd37c2a72a5247e0784ebc99677490e9095f2665b1685a16d7dd48261c922897faa2273fc6030db1b871f208a56e20602b337ef10921b8a3164b128fbfef2566fc0db86c27f392eaddf4745aba700d9a69af3d0ad7" - deposit_data_root: "0x1c37b6d2caaa5c7bb785e5cf0f444edc230bf9836cf1c8a27f1a0c5665c50c37" - eth1_data: - deposit_root: "0xd021c86513c1fd866c766cdedb99464a5c9a2abcb4f6a5563ccd8178cc778b0d" - deposit_count: "269" - block_hash: "0x27cdcf3f09175210019c23eac0ac9309bfd56a6654eb9523900ae387500fd99e" - block_height: 270 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - - "0xe199368557ff186641e9a7d5cb05acac6d482b97df62a1011f0b9fd5c03f5734" - - "0x1c37b6d2caaa5c7bb785e5cf0f444edc230bf9836cf1c8a27f1a0c5665c50c37" - deposit_root: "0xd021c86513c1fd866c766cdedb99464a5c9a2abcb4f6a5563ccd8178cc778b0d" - deposit_count: 269 - execution_block_hash: "0x27cdcf3f09175210019c23eac0ac9309bfd56a6654eb9523900ae387500fd99e" - execution_block_height: 270 -- deposit_data: - pubkey: "0x805de41e2e03f52993f465c0cba3886c40488c0ebe7fbbbe65f016e5e6ff971efe2968e09b0d62263cde4c70f6cf8540" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x830ed2435dc71a80676f51b674be560ecd4d7f0b7d2328ccfee8bc207f4480fac75aa62926c2665a00cfdf2a9b5f18cf00313aa3eab4cb83f3224417cbe42a1591ba0260afb464481391930abfb206db57230757da96e033d25ad57ca15cefb3" - deposit_data_root: "0xd00dd9ec8defab31ac1699c60512ddb92c0b66fdb722eeb8f889ce3f9a36e198" - eth1_data: - deposit_root: "0xf20d2b6626fa9a2412b80217d55810beaa23b9d42c0a386624fc99f04622d1d6" - deposit_count: "270" - block_hash: "0x0f3d50198252da6bcdd9fa7a62accd5b21e2c3236ed4434a6e30ecfa9a31c349" - block_height: 271 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - - "0xe199368557ff186641e9a7d5cb05acac6d482b97df62a1011f0b9fd5c03f5734" - - "0xc68746b2b174136c95a60d0312e6eb45b1d289447eeec15e7126fcdf8a85da9e" - deposit_root: "0xf20d2b6626fa9a2412b80217d55810beaa23b9d42c0a386624fc99f04622d1d6" - deposit_count: 270 - execution_block_hash: "0x0f3d50198252da6bcdd9fa7a62accd5b21e2c3236ed4434a6e30ecfa9a31c349" - execution_block_height: 271 -- deposit_data: - pubkey: "0xa367b7c50ce66726561d5f4190607e9c1d3feab41f624b9ecfadb6f52e1b300ef98e8913e8fffb2b65cdbb889ff5fb6b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x918fe57e516de9e415db3fd4f1a78843da0f6f76b71ab4ec9d8f91bea748fcab31a90e25b22d69e98cf83f43502cf4ac08a0cec4a3e7aa21d5b876b9728b079c0640becf699ff07b023431d6f872561c7e35f86e15d3abe0c9b2bbdc452fdad0" - deposit_data_root: "0xa29deb39e17dec8af0ce7d25156ecabd1ebae33d3076a29b4ea8d8b127335672" - eth1_data: - deposit_root: "0xe92c7ae8c37c06c234eed3d64804cec30984b01ae852511077750a8e5ce8e9e2" - deposit_count: "271" - block_hash: "0x4a4ae702e70fa102e90e05e95bc8163f7cee4224b9a8ade2c97c4ad04d2f104c" - block_height: 272 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xa175806ecd8f45f5b8a87219a9c345e9e612bcdfc850bf0b51c2b6a60813d7e3" - - "0xe199368557ff186641e9a7d5cb05acac6d482b97df62a1011f0b9fd5c03f5734" - - "0xc68746b2b174136c95a60d0312e6eb45b1d289447eeec15e7126fcdf8a85da9e" - - "0xa29deb39e17dec8af0ce7d25156ecabd1ebae33d3076a29b4ea8d8b127335672" - deposit_root: "0xe92c7ae8c37c06c234eed3d64804cec30984b01ae852511077750a8e5ce8e9e2" - deposit_count: 271 - execution_block_hash: "0x4a4ae702e70fa102e90e05e95bc8163f7cee4224b9a8ade2c97c4ad04d2f104c" - execution_block_height: 272 -- deposit_data: - pubkey: "0xaf379c89e4f4bec6b942c9e2d8fa6ccd2ec13cf421d80c987af53d5217c932483c4ed17cd1a9c9d7174e2546ab16ebb4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa49410f5cc90cc268e78c2afa27c8cf129f4da16397953dc383e3eafb1e797faa9bb0e9649502829df2c2cf2b7cd715d0d8be34d1e8cc2706933290ce42bdd8bc1a1bcbab9defcdbb0e85309ca92a7c7c0df00b1ba01dd13e74834ff17788d0b" - deposit_data_root: "0x677f04382b7df90d9e30533f0fea1e1193e6a71e0f3d2fc6621957b406a2d39e" - eth1_data: - deposit_root: "0x734e5eceb4244b23ed80e4a9d91f6830c403c9e37a27c8501368ddc1b9bc1b25" - deposit_count: "272" - block_hash: "0x615eaf0c08bbd375884ea9de1f7261253ab51971c2b1d8407fa63982d4d6feae" - block_height: 273 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - deposit_root: "0x734e5eceb4244b23ed80e4a9d91f6830c403c9e37a27c8501368ddc1b9bc1b25" - deposit_count: 272 - execution_block_hash: "0x615eaf0c08bbd375884ea9de1f7261253ab51971c2b1d8407fa63982d4d6feae" - execution_block_height: 273 -- deposit_data: - pubkey: "0xb6c5b136625178a485ee2eba1ddadc84b55e42c0e5a7ebfc8e5e2db651baf3c0a7cff6fb8e94dfccb1ca0a0d48b7496f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8dba1b2ce9ddb0488fbd27b8f0e760844e5242b4a70d188a0fe5e7602161ca591f09b3ddeae404795f02df26930d00e80ef42e30d4d09fd5632c9dde165519725ff4442dc3eb9d86056a5afbe401a5568674ee6f7dbc7f1ef9d5e7e8ec6d4a74" - deposit_data_root: "0x984f88c7b8523c5a9c9268002fa51558131645074051f28869233e59bba2bb64" - eth1_data: - deposit_root: "0x0b1169b7908d522037d597df82f0e4821a3f141ae329527275e975e1f06b44f3" - deposit_count: "273" - block_hash: "0x9a12613dd49a16c089530e40aa1c8f06f77c852479a90a28387d068d5884ba45" - block_height: 274 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0x984f88c7b8523c5a9c9268002fa51558131645074051f28869233e59bba2bb64" - deposit_root: "0x0b1169b7908d522037d597df82f0e4821a3f141ae329527275e975e1f06b44f3" - deposit_count: 273 - execution_block_hash: "0x9a12613dd49a16c089530e40aa1c8f06f77c852479a90a28387d068d5884ba45" - execution_block_height: 274 -- deposit_data: - pubkey: "0xac210cfd8d8d9547120ba0a786a93318beb172471e9711b764db71376898bc28037160396e32be311a1823e585e8f524" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9917a489a4d766025fe21104187d687a191ab135e68f44282e597ab9bee1197d4cd037d59051d36197be3854c2429964138486367635adc03360ac053251a06df519237d2fa0b72b8540feeb5bb5e6180f137bbbcdc250aa191a62fb9294f33e" - deposit_data_root: "0x6d90c6f3641efb6dce19f2efb93fc24439111dbbcc6cdcdf60a2f4c0019af2dd" - eth1_data: - deposit_root: "0x9604fd80f34acd50e4b641e866a440bfd4697939c358ac3bc91bf25352a8057a" - deposit_count: "274" - block_hash: "0x439db7c4143f986853029b0ce493c55804bf4a38e08c9f2bbc19ff22b1a185e5" - block_height: 275 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xafc140dc42b75f6f96d56352af93e6d41d56ab68fe4b90981a6ef2aca4a11b4f" - deposit_root: "0x9604fd80f34acd50e4b641e866a440bfd4697939c358ac3bc91bf25352a8057a" - deposit_count: 274 - execution_block_hash: "0x439db7c4143f986853029b0ce493c55804bf4a38e08c9f2bbc19ff22b1a185e5" - execution_block_height: 275 -- deposit_data: - pubkey: "0x8771e8df435244648533c638f725d292deaa9ef3e098ccce30255f860156f9101aea2ab56199d5a8cd2042af7ea57e33" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x813560e954b791856c97ff6bf3bc645f75d58e88a405d804957362b901ff360f625839be68c9c612da31cac55f41b7d009dd46e7847de8ff5c78594ac44dc2a5c91ff1330e1f44e3c8398e44a0021e5508afc438bc3f2d88fc9fee40b6171386" - deposit_data_root: "0x6cab9ec916767574e8a430f9d5eba9d27c0a90902ca458bb94ce0f50c6d6ebf0" - eth1_data: - deposit_root: "0xd55236a200b36a4aa5e760c3135bd62b4ac05255ee43580b5d5bf16322ac92b2" - deposit_count: "275" - block_hash: "0xd095a783f5860659c4fbd8e22b2bbb739d9fe8b974da64831e52c4839bc2f8b8" - block_height: 276 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xafc140dc42b75f6f96d56352af93e6d41d56ab68fe4b90981a6ef2aca4a11b4f" - - "0x6cab9ec916767574e8a430f9d5eba9d27c0a90902ca458bb94ce0f50c6d6ebf0" - deposit_root: "0xd55236a200b36a4aa5e760c3135bd62b4ac05255ee43580b5d5bf16322ac92b2" - deposit_count: 275 - execution_block_hash: "0xd095a783f5860659c4fbd8e22b2bbb739d9fe8b974da64831e52c4839bc2f8b8" - execution_block_height: 276 -- deposit_data: - pubkey: "0xa3c94a3ec9d463a4929513b426cc91887c7e09fd038314ada8e5e7a8ce204a7a20247319f91c0746de0e241550aef4bb" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x99b1d155a15927ce6ca47e70b7ded4d286509f134e2c58a85595e7bedf9eb7d27408cf7638df5cc64969ee5a502644d20486af547bf3518937fe1253259f9d0bb2e509b1c6b1b0f8510950345522a82927de2d8191aab3bb4bb0ec1cb94a03f9" - deposit_data_root: "0xe237d745c0911cc07e84746c0e519b09b08b97b06b530a9ddc4e887d53bf8b81" - eth1_data: - deposit_root: "0x5df671c2350221cbf76630d9cf5d5ba52a568e846e24a45f2631a577507011c1" - deposit_count: "276" - block_hash: "0xa5113dab93745a4a0e8e65ef5f666109d653178cc86cf033bbdedffe8de244a0" - block_height: 277 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xd2f50982286bcc1b17c7402df3f3c8e5ccb7ac8516cae8783aa22d003b1838ce" - deposit_root: "0x5df671c2350221cbf76630d9cf5d5ba52a568e846e24a45f2631a577507011c1" - deposit_count: 276 - execution_block_hash: "0xa5113dab93745a4a0e8e65ef5f666109d653178cc86cf033bbdedffe8de244a0" - execution_block_height: 277 -- deposit_data: - pubkey: "0xa647de716dfe7d82e529344bf832f2ac603d7067a515ebb7148fe69cc4330c1c12d3caaf16d6df3a3483574d49a296dd" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x93620e7602b38582df7080d48213f86a395186fe16197167e99e8c555d0a7a2f8b818bf2cc884608059212ae1090fc21135cdd2285e1a96271dc200bae45d68bc30a6053a68c5387557827685bdc3717b1754a22473b9193b2ed08fe12936e4b" - deposit_data_root: "0x454bf4695084db5d52745a0c0fcf55d46b16d40fd982c40df3e64ca30abf2eff" - eth1_data: - deposit_root: "0x2cb46c5f7addaa0c11d5f2a617f116ee53714804de67624d12aaed20ee03e07d" - deposit_count: "277" - block_hash: "0xc16cb9437067b34e14b0db82231c2466967cf85459de2d923b92fc39afedbcb0" - block_height: 278 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xd2f50982286bcc1b17c7402df3f3c8e5ccb7ac8516cae8783aa22d003b1838ce" - - "0x454bf4695084db5d52745a0c0fcf55d46b16d40fd982c40df3e64ca30abf2eff" - deposit_root: "0x2cb46c5f7addaa0c11d5f2a617f116ee53714804de67624d12aaed20ee03e07d" - deposit_count: 277 - execution_block_hash: "0xc16cb9437067b34e14b0db82231c2466967cf85459de2d923b92fc39afedbcb0" - execution_block_height: 278 -- deposit_data: - pubkey: "0x97dae6c47e2b695734163c4e12ff709e7d63879fda8192ca26a35501dce45fd8748b5ed04519e829d61e5e7a3e379dfd" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8419026bb209d08a2db619c23985a483e4a42af20e10715893985ee289a000c990c4f33693780a3640ded33dd21fd2200781677f590164d65528a4f1c047443e619930b7a3ab3be00e7a6c0df70fa505da34862fb9a539a531c237792dc0ab79" - deposit_data_root: "0xdd6f4ed981c18b7b2e7bb2f4e0688708fd069d405b0b087315ff02eefe2984c5" - eth1_data: - deposit_root: "0x3ecdd5c5beb8def23312392aae577b8950c66c902af8bf99de73b96656be003b" - deposit_count: "278" - block_hash: "0x8bd6fb2e4d0410f8eae7eedcffbc2f9ae0e0a31b9dc9d6de94b0bf7668691eb1" - block_height: 279 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xd2f50982286bcc1b17c7402df3f3c8e5ccb7ac8516cae8783aa22d003b1838ce" - - "0xd795029ab5b69d8e37a9ff4be1b60af4cf9805d75bb3e2b61fa05fb9decb98d4" - deposit_root: "0x3ecdd5c5beb8def23312392aae577b8950c66c902af8bf99de73b96656be003b" - deposit_count: 278 - execution_block_hash: "0x8bd6fb2e4d0410f8eae7eedcffbc2f9ae0e0a31b9dc9d6de94b0bf7668691eb1" - execution_block_height: 279 -- deposit_data: - pubkey: "0x897c752e1ed45c3ab22146fe595fd08175a828c69e1e3243e6e1e644792b0bc979924123c0cca7dea405b074e214880e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xafd9fdd20324edeeb6283b1a45e7178f6419260b0581cd71ea46b392d6ddb5ee3b7dcef33bf889f401e6b78c5d3766370f25200d87ace14ebc81992a6c0c7552bced661cf33b1ec285d89ebe94c7159e0f0d78543e1d1362789e54729bce5834" - deposit_data_root: "0xbefaf00ec4412758aba9bbd73a7a8f4c56dec0523ca462ae5dc6c1f553100248" - eth1_data: - deposit_root: "0xa3e6322b128ba1eb9cab42f9e9de032e5ef934e3395e3c070c2e3877a9262640" - deposit_count: "279" - block_hash: "0x00ed7d8bb7cdd6256ac0d7f1ad7b9e34302d9e119e9db4078fbb19b8d3541685" - block_height: 280 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xd2f50982286bcc1b17c7402df3f3c8e5ccb7ac8516cae8783aa22d003b1838ce" - - "0xd795029ab5b69d8e37a9ff4be1b60af4cf9805d75bb3e2b61fa05fb9decb98d4" - - "0xbefaf00ec4412758aba9bbd73a7a8f4c56dec0523ca462ae5dc6c1f553100248" - deposit_root: "0xa3e6322b128ba1eb9cab42f9e9de032e5ef934e3395e3c070c2e3877a9262640" - deposit_count: 279 - execution_block_hash: "0x00ed7d8bb7cdd6256ac0d7f1ad7b9e34302d9e119e9db4078fbb19b8d3541685" - execution_block_height: 280 -- deposit_data: - pubkey: "0x90e6673aa3258396ea9b5e5d4c53b076fc93d4f408008343e44f5d4e49e408942c764d454b435be080ed8e4328ddcf1f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xad7a438ba41385b1078bae963e814a92e922a83efebe6aca14ce1a0a1754ec7d882211ef71ef55bcaa4d87e75b20f9b611837e28ab711d1874863a151a5ebfee310b649ce58aeee5fe7809dbf813a0d1e5786cbcf9286fbb6d649939c781339c" - deposit_data_root: "0xdc9c1d97e917f6e25ae888d726fca078b28921a5bb9f1ea669ba53b18caaa5c0" - eth1_data: - deposit_root: "0x7b3c923bef540d0db79619c5bfb8a2abf74f57c23a1cfd4dec6410feacb6b943" - deposit_count: "280" - block_hash: "0x0067d9ff3bd86de63d54114d41770e5b0cb7c8c5120b19530eab3d3bdfe79836" - block_height: 281 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - deposit_root: "0x7b3c923bef540d0db79619c5bfb8a2abf74f57c23a1cfd4dec6410feacb6b943" - deposit_count: 280 - execution_block_hash: "0x0067d9ff3bd86de63d54114d41770e5b0cb7c8c5120b19530eab3d3bdfe79836" - execution_block_height: 281 -- deposit_data: - pubkey: "0xa7f7669994d4503c6390a44e7b64103861f78d60283580e19d0898948ed55a0b16b6c8fcd86e14341fd9a5974e4eda5d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x837a5a91adc8bd9b06a5b116769ad796b673f5230741af2e8ce61d59c7f3eb80b1a896f36af0f7b1d72c612cdf51eec7154395d1cebf833e0329d4ebc118a0708b2d2429d13324157ee8056eba25c5e0ff6a3b4212c19e437666151de56a0e46" - deposit_data_root: "0xc760b2ad57feef344b258de4d1ecb020fc392a45187f5aab606d97a308a63ebd" - eth1_data: - deposit_root: "0xa06cf7368dcd4b336a8769c80e7ade8ef0d8900f5abe104cececdef2a22f73dc" - deposit_count: "281" - block_hash: "0xd67c87e9d0958db9609fdb7e4a3246578abbbc9e36517a6c224196c1d726207d" - block_height: 282 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - - "0xc760b2ad57feef344b258de4d1ecb020fc392a45187f5aab606d97a308a63ebd" - deposit_root: "0xa06cf7368dcd4b336a8769c80e7ade8ef0d8900f5abe104cececdef2a22f73dc" - deposit_count: 281 - execution_block_hash: "0xd67c87e9d0958db9609fdb7e4a3246578abbbc9e36517a6c224196c1d726207d" - execution_block_height: 282 -- deposit_data: - pubkey: "0xb8ad8a758c02b9a1769a5f883a41eee5a516165cd2ae19749d60ff634f1175940ea569244830265b2f59b2aeaa434478" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb55d9ffeab85b135b7e21166db97d4defbea12b1beebc795a694062cfcdc5de3db44e1810539c8b22bbc2a930b4cc0fe102a8b233c45b958a7db52b28bcbe9192e191b967ba76f9fce60d4bd30ebfcf0c156b298ed99cbf1df359da8f9b089c7" - deposit_data_root: "0xd224e7399aef34aebab912dd931e8029d849a75e46d52d3e7af7ef795089aba5" - eth1_data: - deposit_root: "0x41e107ab1e7255ca23c4f66b6afaa812e789a822664683cf158c481a32a62e10" - deposit_count: "282" - block_hash: "0x8e6819b492671a514f3da060880e5379b0b42de61df7089d479d883c2f25e4a6" - block_height: 283 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - - "0x5f703e4b8197692608370e14e3f2f2ae87e4817939d05d095009486de7ebe0ed" - deposit_root: "0x41e107ab1e7255ca23c4f66b6afaa812e789a822664683cf158c481a32a62e10" - deposit_count: 282 - execution_block_hash: "0x8e6819b492671a514f3da060880e5379b0b42de61df7089d479d883c2f25e4a6" - execution_block_height: 283 -- deposit_data: - pubkey: "0x95037a05a0a6a90acadb5cc2156117378faa1c1eb79cacf3b91835e268d7855960e638dc34bbc192cc6b8ca6b942be04" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96ffe4fec99f4e61dfeddeb4e0ce1db5d3f2ec1c7b5608a27dbea3a684705aadd979a4c48ed703904b1afdab4ec410e717a8d892b8c881d95fc0cea3df434f4a2a736b75e12cbfa65354d1452f1c1494679b0c2f82e0278bac0e727cca54ae5f" - deposit_data_root: "0xa84635d4c80545e945a05b992bf4e018466bf90ce77efbc6c4afcd86e7c40480" - eth1_data: - deposit_root: "0xf45ea7c111196d94551e75f4827dc54da7b27159b61f0157001cc501e449ab11" - deposit_count: "283" - block_hash: "0x208b9d89c955086f385701344c89f0f7f36d8a12f8126654ff46fffac19738cb" - block_height: 284 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - - "0x5f703e4b8197692608370e14e3f2f2ae87e4817939d05d095009486de7ebe0ed" - - "0xa84635d4c80545e945a05b992bf4e018466bf90ce77efbc6c4afcd86e7c40480" - deposit_root: "0xf45ea7c111196d94551e75f4827dc54da7b27159b61f0157001cc501e449ab11" - deposit_count: 283 - execution_block_hash: "0x208b9d89c955086f385701344c89f0f7f36d8a12f8126654ff46fffac19738cb" - execution_block_height: 284 -- deposit_data: - pubkey: "0xab152bc78eada258d68785ad491422f64ffece7e462ac59ba92ca919f6e2ecb3c3707b34d6159a967b4dd7590c4321e0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa62cef8f23182080da9f5566785c5f614bb68b7ed2480551ff248ba0ff398af1125437bb1703598a3726757e6aa242f30c73e7f1833a07e3944fa370bb35a17bca08a6a65988a8cca0cc544489bebc4f7089ac288694483aa3f0d14913d11bde" - deposit_data_root: "0x7a9db3084c5390172ef580652bd82ec524081fa698d216f8680acddf3598b5a9" - eth1_data: - deposit_root: "0xb14450ae34030b8e3b7357bcd9b403267d507c15e5006ee4df1d5fa5dcf8dafc" - deposit_count: "284" - block_hash: "0x705a548444db8857275b1e4daff4e049715ba5c3cd31e9c9ea40d7aa767f76cb" - block_height: 285 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - - "0xd2b6e86410a770e340ffb7de6ee5cf863a85940c79573f83f25f86aea119f640" - deposit_root: "0xb14450ae34030b8e3b7357bcd9b403267d507c15e5006ee4df1d5fa5dcf8dafc" - deposit_count: 284 - execution_block_hash: "0x705a548444db8857275b1e4daff4e049715ba5c3cd31e9c9ea40d7aa767f76cb" - execution_block_height: 285 -- deposit_data: - pubkey: "0x847230d7b775a10cb13cbe8b80b7d2e5e613043af2913729fde2404485cb4dcff11fbb08487c860125c9d4828686818e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa97dcbec769d32f79ab1ffb179002460cf80afd587cd7f19c6f8761af07a8819cd82a64d82917e208dede5fdda3392e008516c97578f0fa1f56201f010b00e3a7dcde2d8ce22cd02d24663540889087ba0f7011af978035591f41a26b00d4455" - deposit_data_root: "0x71aa303f97c1545130a878320efceac5ac755201d7435beb0a6a5bbbd9f76432" - eth1_data: - deposit_root: "0x89e2e3db69c005b9af9f2f52537e98e4d99b15078574bfbb0542230f161d7bf4" - deposit_count: "285" - block_hash: "0x70ce0316c6eb65c0b81f178a0f234dd62a88b9f1f751b77974158bc26cd78152" - block_height: 286 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - - "0xd2b6e86410a770e340ffb7de6ee5cf863a85940c79573f83f25f86aea119f640" - - "0x71aa303f97c1545130a878320efceac5ac755201d7435beb0a6a5bbbd9f76432" - deposit_root: "0x89e2e3db69c005b9af9f2f52537e98e4d99b15078574bfbb0542230f161d7bf4" - deposit_count: 285 - execution_block_hash: "0x70ce0316c6eb65c0b81f178a0f234dd62a88b9f1f751b77974158bc26cd78152" - execution_block_height: 286 -- deposit_data: - pubkey: "0x811f122742c9f58ceff4b9b75ef59d8a8b5553313c027e18a21f417206d72b66d225c30d9f9f5dbb648d68e537a95cd8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8d64c45dec8fd9624e5a673b4be330985f42fb04e32ddcb14f8463e92c8534e8c9dc7ce6dd324d9e46d08535d67024bd0f3d2eb8afe1c571969f7054eee365376b6ef65dd2d79aeaf4b9e065fab99a08f54f7aa4eaa608f542be51155c97c943" - deposit_data_root: "0x8c2d9422f7f6da9f58a22bf2958c28614b38612f6599a09dbbfb3b6b05a2c9de" - eth1_data: - deposit_root: "0x39ed089d07634871eab8445a2e27720f969a75194f7e739b28632596d51afd4c" - deposit_count: "286" - block_hash: "0x0ad81ac5b5624db6ae564e1e55a039c9ff07abbfad8541f36b1499a38e34ff01" - block_height: 287 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - - "0xd2b6e86410a770e340ffb7de6ee5cf863a85940c79573f83f25f86aea119f640" - - "0xd8e36fc1ec59065a6d776496630ecf9de9417b10f4fba2873ba760bf445dfe59" - deposit_root: "0x39ed089d07634871eab8445a2e27720f969a75194f7e739b28632596d51afd4c" - deposit_count: 286 - execution_block_hash: "0x0ad81ac5b5624db6ae564e1e55a039c9ff07abbfad8541f36b1499a38e34ff01" - execution_block_height: 287 -- deposit_data: - pubkey: "0x875b740b7de5cd6db43823c3dabb3e0652b132a2b0c7593383420f31c452c3e772885f083932eee347c94ff2411367fe" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x870a4c9e1e8bad7573ca85cf96c85fcee530e3918c0fd5231945d44b93668b09dfe394fee7df5f34641f9a2100d7f8c100758148ba94822633c24ce5c8abfa4922f9dc83ec659a471cb791d2c8f50bb63879125bb240da3f3ddf073034022974" - deposit_data_root: "0x95dae1cb1d3ceb2b954761d7e3994dfcf890f0af2d932c8ae16a0305f4172e7c" - eth1_data: - deposit_root: "0x091a9d9c1cb63e60b98a645f4b92cb819e69c9d72a1a22bbf4ce0aab06fd8f4e" - deposit_count: "287" - block_hash: "0x0a2ee6676a0013c27224c31e35877a9f41c87bfcd451d50a496f62094cdddf59" - block_height: 288 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x004f564bddfe12ca7aeb4708f48538e1d5fe4b3ad2c8b08be819236aa2c67ec2" - - "0xb96037b213deff69adf6fc2ef3ff23ef01b2a19f085f6cdc4395ef55d13f1eb3" - - "0xd2b6e86410a770e340ffb7de6ee5cf863a85940c79573f83f25f86aea119f640" - - "0xd8e36fc1ec59065a6d776496630ecf9de9417b10f4fba2873ba760bf445dfe59" - - "0x95dae1cb1d3ceb2b954761d7e3994dfcf890f0af2d932c8ae16a0305f4172e7c" - deposit_root: "0x091a9d9c1cb63e60b98a645f4b92cb819e69c9d72a1a22bbf4ce0aab06fd8f4e" - deposit_count: 287 - execution_block_hash: "0x0a2ee6676a0013c27224c31e35877a9f41c87bfcd451d50a496f62094cdddf59" - execution_block_height: 288 -- deposit_data: - pubkey: "0xacbfea617a3cbc5df59da38133fee49b25a4a27aee878184acbd8cad6c72a2ffd5cf31812e4ded11d7a73c6c7a9a7cb8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb261f12cd1af634ddd037f62c1930120938421469681c3e4ad8abed21ba550a1b60d91fec994000e28bd880da3de709c0af2408c80150b438cf318791dc169983785152bed21b01117f698ee6853bf23948501289e4ccd63d7d87b70d82484a2" - deposit_data_root: "0xc5c4f40555719fb6d08ab90de29f01a80adde2ede7f5a2338ec0ae24980480a6" - eth1_data: - deposit_root: "0x313a8e2fec006ca89b4e20c0945b2f7ba336aed9695b4b03bc06608712cde390" - deposit_count: "288" - block_hash: "0x7895648d903ca4cfc86649eb22f8d1c758141f8d3aa89f938f2f25016481435e" - block_height: 289 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - deposit_root: "0x313a8e2fec006ca89b4e20c0945b2f7ba336aed9695b4b03bc06608712cde390" - deposit_count: 288 - execution_block_hash: "0x7895648d903ca4cfc86649eb22f8d1c758141f8d3aa89f938f2f25016481435e" - execution_block_height: 289 -- deposit_data: - pubkey: "0xaae696196c2730c93099a70860ca5efa86f84e5841e05aaeab2619a772f23eec46d4c72a31b0e458c85409b511b498a3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa99138f2331c3cf13064847011cece90e314742f897c5a629912cf2339ae8e2ee5375bd3a2bd33e164a03b3adde0d40f0243c57233e12f7d5f61328bb6f4e2516bbc784dd6fcd3ca814cb8658499516267b4b1aed34eee7dfcc1b9920efcbe4c" - deposit_data_root: "0x844286999134010ba50c2a62fbe3af61cb39b66dcb6c74fbd58a9aad32331c73" - eth1_data: - deposit_root: "0x1aaf2dc02a54384847050da49a834ff3a3474b4419885b2e37092c885c2a5764" - deposit_count: "289" - block_hash: "0x74d07cd3aacb5ed6004cc7a76696a60a64fd2f8a1367d1070b3e9e70316cbc58" - block_height: 290 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x844286999134010ba50c2a62fbe3af61cb39b66dcb6c74fbd58a9aad32331c73" - deposit_root: "0x1aaf2dc02a54384847050da49a834ff3a3474b4419885b2e37092c885c2a5764" - deposit_count: 289 - execution_block_hash: "0x74d07cd3aacb5ed6004cc7a76696a60a64fd2f8a1367d1070b3e9e70316cbc58" - execution_block_height: 290 -- deposit_data: - pubkey: "0xae0825786e4f5ada18f212b067a394b52e60b37c98bb22ccdf0416d27b33029678de976370eea92c384ae44bca79386c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb27632627c427944c2fc88f3cf387c3ae3b01bcbdd66fca2e0342c8d99608e86943a7f85783609c2f52bca77c4c703561734b382e423db9b639655ad298c1278d906259b9d73d8a050e78ce06433a6ad5c0e18f22edfdd6e59f8bcc4eff47262" - deposit_data_root: "0xd4fb8486399925af6ee9cb0675686bf197754b6115bb082560b529ee4b0bc6af" - eth1_data: - deposit_root: "0x799fbeae76281fc3802633b4e9cf0e96fccd7cdfeef10763f38d71a9362264f1" - deposit_count: "290" - block_hash: "0x7394bcdc109326766ef4bbbc9a79a2e3a0ec7ca68af666caee2628105e29da04" - block_height: 291 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x7705b49fc5157cced1429cc52e57d903c5bef2f85f33041d5cfb966557058b7c" - deposit_root: "0x799fbeae76281fc3802633b4e9cf0e96fccd7cdfeef10763f38d71a9362264f1" - deposit_count: 290 - execution_block_hash: "0x7394bcdc109326766ef4bbbc9a79a2e3a0ec7ca68af666caee2628105e29da04" - execution_block_height: 291 -- deposit_data: - pubkey: "0x95ef906a741f50b59bf8fb77c134abc0534fbe7d0c432832d2d1c0ff9bd20090b424df54ffadebb9cc43e5f3812a7968" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x86e9c1163ad77136831a5a21680ae3b20e22ea656c108e3db81c698526b2a0f453df4b18c563a6491b4b627f8885e79611a4d5b71a2c2dc72812fd0219314c4ff2cd561cb8acb01fb6b03dc4052b7bcd5231053cbcb28d47da3e6bb3442b5018" - deposit_data_root: "0x6edb20c206b0c315233ef2716988f6043c49f1a557b934ca778841f252256d33" - eth1_data: - deposit_root: "0x87c3659d83ea26cbde44dcc3b5b4c4afab70c1c3c1345cd11514aa7a325b66f0" - deposit_count: "291" - block_hash: "0xcf1ebd89ed9130987c74ec2d4af8d83006364bf28784baaadd4c169ecc03ac75" - block_height: 292 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x7705b49fc5157cced1429cc52e57d903c5bef2f85f33041d5cfb966557058b7c" - - "0x6edb20c206b0c315233ef2716988f6043c49f1a557b934ca778841f252256d33" - deposit_root: "0x87c3659d83ea26cbde44dcc3b5b4c4afab70c1c3c1345cd11514aa7a325b66f0" - deposit_count: 291 - execution_block_hash: "0xcf1ebd89ed9130987c74ec2d4af8d83006364bf28784baaadd4c169ecc03ac75" - execution_block_height: 292 -- deposit_data: - pubkey: "0xac3a33ffd0eb0ecdb7baf1a5a4d8fdbd7c2e7558aa073269d2f9b7e9e5c7a1402c30efdb34fd7d268ec5f64db92a76c3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xad36e7b3da78886b41f5e61ad0bec7a5a7f68fb9b6d5bfd0ba5b75c59dfc6ada6b33da3729215c23122d812abc4e8eda06f88f7e414e79224eefcb4bae2f798512cab1cb96f5f1c1ed5e8b81c97967098a3a38b96a1bb2e19bbc819de9342342" - deposit_data_root: "0x7e435b81ff4fcb487b26cff3b131c5453c05d167c6617c8865879b51bf8fb04c" - eth1_data: - deposit_root: "0x53252143b0ff828eec2c69855e5ca87dd5153f5df8a7a24603031e62fc7a2980" - deposit_count: "292" - block_hash: "0x1dec0f6cbd0fdfaeb411d008ff1b21c4cab80e38ddab4385464828921e8903d7" - block_height: 293 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x0730794fdc9fa73177b77e150edf2e164ec8d1917f76a71e085eefdaf0bd3835" - deposit_root: "0x53252143b0ff828eec2c69855e5ca87dd5153f5df8a7a24603031e62fc7a2980" - deposit_count: 292 - execution_block_hash: "0x1dec0f6cbd0fdfaeb411d008ff1b21c4cab80e38ddab4385464828921e8903d7" - execution_block_height: 293 -- deposit_data: - pubkey: "0x8b1d9ba63bdc005529f0f6e2f442e6649adcec11e0db1d643b3d3e09307cc8c5b3fb84049941b8692f10799011c246c9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb331295d92ea1dde46a7149810b7348a1cfc004b760326a35e61c95afde55c3e1c9798b3e94d8dc8b8a4101746600b470d2b693074eec42f90347e22ddb6dcf539de7b4c9d0bc2ad6d21e59d81228831df635658789f3d1dacea605ddc391719" - deposit_data_root: "0xaa148346b9b8d2c5c24672977c58e8c35edacef56bfd81028f3fecba2b2442c5" - eth1_data: - deposit_root: "0x34e1e67726ccca66adad1c4d54baba739c7c52955a01b7cba29c3d8f2317f5e2" - deposit_count: "293" - block_hash: "0x5808234c115cc61500e2a564049a17037ee5108aec392d429f03f58ba8d16443" - block_height: 294 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x0730794fdc9fa73177b77e150edf2e164ec8d1917f76a71e085eefdaf0bd3835" - - "0xaa148346b9b8d2c5c24672977c58e8c35edacef56bfd81028f3fecba2b2442c5" - deposit_root: "0x34e1e67726ccca66adad1c4d54baba739c7c52955a01b7cba29c3d8f2317f5e2" - deposit_count: 293 - execution_block_hash: "0x5808234c115cc61500e2a564049a17037ee5108aec392d429f03f58ba8d16443" - execution_block_height: 294 -- deposit_data: - pubkey: "0x8f9ec3c7b0f5793f3056fee53b16af2874bef12a90bd0ecf6282d55ce6ba70a07071df4e3861ad99da8753677a2a74a3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa81d1bd6fb813b55c286bae7e79f9ad080e65865bc0c34f8309876e22f1db6e06b87abf4b0fbda1ade851d0914f8c4270d2abb19efd831a116a040180a9eae89daa1ef6a69748984c1b8029d33ed4e278d26226833a0d05762f94c389b758b83" - deposit_data_root: "0xea2af2f7a4f8409ff112a26b16f36e6192581fd5c633f90b165668999c1f9e1e" - eth1_data: - deposit_root: "0x21ec4811b4490a5d9c97b40830607f18ed6decc77cafc3328b487ea076ec4520" - deposit_count: "294" - block_hash: "0xf86216620bf16f8591322c0b32fe4c594de559b382aae1c535be72771d8dba24" - block_height: 295 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x0730794fdc9fa73177b77e150edf2e164ec8d1917f76a71e085eefdaf0bd3835" - - "0x7bad1e86995476e6d8c7a8a05d659145ff02c84f1b0eac70e5964c395c14f8e3" - deposit_root: "0x21ec4811b4490a5d9c97b40830607f18ed6decc77cafc3328b487ea076ec4520" - deposit_count: 294 - execution_block_hash: "0xf86216620bf16f8591322c0b32fe4c594de559b382aae1c535be72771d8dba24" - execution_block_height: 295 -- deposit_data: - pubkey: "0xb083923ec58581ce049677eb0df25ff6ea0e29938881a9e03ecedf4c7482ccd25dbd2f8a15d6bbdf8eca74a74f444e40" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa7ed5863f832a1b984ce9993b33cdded121815178c70538b0926a3f17452142ba546cb751861f739c4303940664348f908ab344bee4e62aacdf26d25ba25cb32b1269f3fd2defd2b740788f2000021c07f0ac20ed927745641f3ea74464ff433" - deposit_data_root: "0x450d67be526c8cc2dc5b23a24ffbd79a03dc5b4e9685285e381e598bb478b958" - eth1_data: - deposit_root: "0x331ae72b1a07010b6967e88090dcf730d1c721b54900e636c7bcf23a4b511e7b" - deposit_count: "295" - block_hash: "0x5e382493002589f3487d5ef95d6933de04c0b65a592691966854d900eaf32f74" - block_height: 296 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x0730794fdc9fa73177b77e150edf2e164ec8d1917f76a71e085eefdaf0bd3835" - - "0x7bad1e86995476e6d8c7a8a05d659145ff02c84f1b0eac70e5964c395c14f8e3" - - "0x450d67be526c8cc2dc5b23a24ffbd79a03dc5b4e9685285e381e598bb478b958" - deposit_root: "0x331ae72b1a07010b6967e88090dcf730d1c721b54900e636c7bcf23a4b511e7b" - deposit_count: 295 - execution_block_hash: "0x5e382493002589f3487d5ef95d6933de04c0b65a592691966854d900eaf32f74" - execution_block_height: 296 -- deposit_data: - pubkey: "0xa41e4e990ba7effeb504f4f0c27924c90c76ec433f2cc59d65619ce9796b80db1b2a6c3fdcd3a3b5ae798c65433ff911" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa3eb87a76711659f7821c30c915d68c8853d948e1ca08da89c5f20b390f1c65feea7374c2040b3bbf9c6496af37afbaf09ef6a060d5370a06318053d845a4aaf99e82852a8ccb4dfab2ee3980f8cfda07b08dfdca5c15ce73a67fcd4d5979ca7" - deposit_data_root: "0x056b0a462a8e8ab1f9cd2d1a3428e3a102de98b075968c605d95bb65dd516095" - eth1_data: - deposit_root: "0xc16e10784846d8fdd7bb158f065db835af24031e348baeae33ae24ede22ec3e1" - deposit_count: "296" - block_hash: "0x8ffaa2662024d089a5006527ba28bb380b041cc2e81e2421d4deaededb9d3d61" - block_height: 297 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - deposit_root: "0xc16e10784846d8fdd7bb158f065db835af24031e348baeae33ae24ede22ec3e1" - deposit_count: 296 - execution_block_hash: "0x8ffaa2662024d089a5006527ba28bb380b041cc2e81e2421d4deaededb9d3d61" - execution_block_height: 297 -- deposit_data: - pubkey: "0x8fb481a72fde68d6b21d0f679bcf64c9986e88acd27874cdfb4d2ae7a84e7dac89ca6afd072bdc503b15342ddc899b94" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83f4b1f8e47b6c688f1068e9ecfe223b53090656d44396b24f7192e6fd7cdfd66f96760b8fca2b01c57e0c01c323952008d6e33dbb0fbee95c36dda724fbecfb07dd99ce251bce32d22e2ce36bc45b9e59ffec6ac9aa2e67a94953ea1deab2c4" - deposit_data_root: "0x8bc82d0cf82a2499dd2b717cfde2c3e68bfc128763e250a4fb50e3f0122accf8" - eth1_data: - deposit_root: "0x1add0cbc74e152fa595c6dc87e2d5df9699025429dc658682c635977d03fcd6a" - deposit_count: "297" - block_hash: "0xf305397aeec554ea12622d5b37c601710070c0f4856e2b0170856c55f80e07a9" - block_height: 298 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - - "0x8bc82d0cf82a2499dd2b717cfde2c3e68bfc128763e250a4fb50e3f0122accf8" - deposit_root: "0x1add0cbc74e152fa595c6dc87e2d5df9699025429dc658682c635977d03fcd6a" - deposit_count: 297 - execution_block_hash: "0xf305397aeec554ea12622d5b37c601710070c0f4856e2b0170856c55f80e07a9" - execution_block_height: 298 -- deposit_data: - pubkey: "0x9792ac6f48e8f90fdabf06dd862bfa48612c72685f259f7f69ce201889daf43e220bbe3795554d1973b497360b81311d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83ed426c80869348bf799aece915aa4c3ecd715df0d83c443758db8d74be8dfec2707bb8cd41a65117b7f484998f59d715cca0f03903248374af59f3013d9a0ea7ec0dff855c632ad47c695186736c62ad52d3c0131f5227bd8d2a977e4b574f" - deposit_data_root: "0x377acb0a185e15e235bb479f2c7a6eadcdebbe04e96282510656c1c8643c8f08" - eth1_data: - deposit_root: "0x90beacf75d211ca5fa19ea7a78d25c2e9a72a9b59652192d5b37368a66d7f71a" - deposit_count: "298" - block_hash: "0x141e6a356ffe9bce0d90886c27b5460df7ad03c69635cf28c3a3ffa99dd524b7" - block_height: 299 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - - "0xc60a40ade74fa8bc78d964bb75dbe9e361b8468980dcf4ef30f5712a8e06f20a" - deposit_root: "0x90beacf75d211ca5fa19ea7a78d25c2e9a72a9b59652192d5b37368a66d7f71a" - deposit_count: 298 - execution_block_hash: "0x141e6a356ffe9bce0d90886c27b5460df7ad03c69635cf28c3a3ffa99dd524b7" - execution_block_height: 299 -- deposit_data: - pubkey: "0x8db6b709638a90a292ecc760d425bd26855a8b67e34286987f3c6aefa0811573e106f54b088b3afa8436156fb36f783e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xae4ead5a15f81287a7c7c20d4265d95345637980d27bf0d8edfdb1d7ca2099c9283916e09eae340be93f7db49d76d89b19ae467cecc02bee4d8f47a82a9609db4ed4f5c915c910babbc35d885799b6d6e5e70b9025a6e2e192f8dabe61109028" - deposit_data_root: "0xddc96174c52bb3bdd22b83e73bda24cafc7203e6166d0ede60c079a367d5ac08" - eth1_data: - deposit_root: "0x31dae0097efd30f99ca3f1582a55545fc31326f4046ce06adce7b926155b60b6" - deposit_count: "299" - block_hash: "0x34c74dd45b01879ff4a0508fefbf6fc9adccc408bffa5cfe13e68655115226e0" - block_height: 300 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - - "0xc60a40ade74fa8bc78d964bb75dbe9e361b8468980dcf4ef30f5712a8e06f20a" - - "0xddc96174c52bb3bdd22b83e73bda24cafc7203e6166d0ede60c079a367d5ac08" - deposit_root: "0x31dae0097efd30f99ca3f1582a55545fc31326f4046ce06adce7b926155b60b6" - deposit_count: 299 - execution_block_hash: "0x34c74dd45b01879ff4a0508fefbf6fc9adccc408bffa5cfe13e68655115226e0" - execution_block_height: 300 -- deposit_data: - pubkey: "0x93ad251e308b778815f925a6de6b11bb7f3d4d8d710cafd66cab8be0b91ef0ada350c2ca2d8f5fbc3ff4f002445680f9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa3cf73c28799277452db8bb4e7c5944f029c8228321a655325aac140baf67884f24dd459356818857341088afda6828b06149d514d5b2ff3caf8993ed07129b111798a35a9dc28f526752e6002cee205127893ceea458afb5da8698d0eb4902b" - deposit_data_root: "0xa01227f9122459ac47d9a6b110e72f74ea18ae68b62ca95a0b76bb4166269eda" - eth1_data: - deposit_root: "0x8c2f6e57502350b5d9f26e44419267b541ad91471cab8d5db5ff4184f5bb33a0" - deposit_count: "300" - block_hash: "0x193c858804c50b4119d959c31820b38c2beb2bc36f0e0595213c4030cac59626" - block_height: 301 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - - "0xb8f534205d4b218cd717bddf06a06cae872d995a165506cff2e8c12af6a71afa" - deposit_root: "0x8c2f6e57502350b5d9f26e44419267b541ad91471cab8d5db5ff4184f5bb33a0" - deposit_count: 300 - execution_block_hash: "0x193c858804c50b4119d959c31820b38c2beb2bc36f0e0595213c4030cac59626" - execution_block_height: 301 -- deposit_data: - pubkey: "0xa31e15bea68f0f555e2eb0552f74bb78a755ee77e242799cfeb8a380507d994cf24f46fd86568f46c4ecc7ddcf359ba6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb3e700ee6b7fcd21fe53dee2c22d4da8ca914be0adebfd496396edff50068679a17410d9afbcfffd5f6b0add9d2523e90cdf840ba008e4d58ebe9ebea0115305155c5d3f7b566cbde5f7dd511fef9d543c1fab467e91bac3d4a27ac9ad87957f" - deposit_data_root: "0xdcf69aa4fa002dbef4af6017e7a178c9c553c68e3f3d9352563491ab58bd82b5" - eth1_data: - deposit_root: "0x7615cbc219a8c6a9fb950d887fd850185534967b2a5441902215e584dd2151a5" - deposit_count: "301" - block_hash: "0x79363266a5cff13e09297e640ced33e737db69fdc4e8a649939e36578099a9e3" - block_height: 302 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - - "0xb8f534205d4b218cd717bddf06a06cae872d995a165506cff2e8c12af6a71afa" - - "0xdcf69aa4fa002dbef4af6017e7a178c9c553c68e3f3d9352563491ab58bd82b5" - deposit_root: "0x7615cbc219a8c6a9fb950d887fd850185534967b2a5441902215e584dd2151a5" - deposit_count: 301 - execution_block_hash: "0x79363266a5cff13e09297e640ced33e737db69fdc4e8a649939e36578099a9e3" - execution_block_height: 302 -- deposit_data: - pubkey: "0xafb329bae0ec756fd1be3ee1eac93550b77177d96558deeec2a6a131e35ccdee653189d4f5bb744f492371605c44a9bc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x81839fd0a1d6bcb75751f1c9a3d3b2a8c7812c4e2933d52c0327d69ca7fea84c9ad7c1a4475c88e46d25baeacd41e4e60c5f3ea05c16ec768e011d41c0e3b2edac0d9ae16b4b410313470283ed4086634c080bb7e676493763c459c6d246e15a" - deposit_data_root: "0x3d5f8d118ae4e4fca8223b2b819c1931cd041443d278b6823c9b4aea22bd57f7" - eth1_data: - deposit_root: "0xa80eedd04962726c155bf13aa99c6c56457c99e3d4533ab74a4abe2e961fe9f8" - deposit_count: "302" - block_hash: "0x1c6191f59c7eb6f4c8cc5837ccd23de7600c2dffb62c9e1133248baf3cc4e363" - block_height: 303 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - - "0xb8f534205d4b218cd717bddf06a06cae872d995a165506cff2e8c12af6a71afa" - - "0xa93353e7b0d909e8d19d5d6c466cae15e8e5f2b929758e88d8c35f6ac180c0c2" - deposit_root: "0xa80eedd04962726c155bf13aa99c6c56457c99e3d4533ab74a4abe2e961fe9f8" - deposit_count: 302 - execution_block_hash: "0x1c6191f59c7eb6f4c8cc5837ccd23de7600c2dffb62c9e1133248baf3cc4e363" - execution_block_height: 303 -- deposit_data: - pubkey: "0xb62df9c59793d5e3a553e9f5b9da4c928105e221898f11847e7ec9a60c091a1a56e0f141d64bb5bac13d424a49798917" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x822390897c4b8d9e0f2d0868afe9cfe5efc14f7290a48717071de84200fdf96d5f4aecf2c577d003f4af4c4f59bbbfc802166746f52794eecdc656c4ec845085d053c4d021d45c639567e24825bbc60572747900e54452fb052f45895ed97e14" - deposit_data_root: "0x888daeb2cf145994cbd3f25806c306082dd402d4b4420f0817c5a5a19132cb36" - eth1_data: - deposit_root: "0x334dbf7b9b0cd5d22f79c7dca388d3baf541e430bdd31883fd7402852febf29a" - deposit_count: "303" - block_hash: "0x42bc399b54a8e566243c3ba4e2e1ceeca8eea2b7cd84462b8814229d9f459796" - block_height: 304 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0x5b1f9c36b45f9d1e8495781e9d8290f328e09b9ec8f382b801aa6354b25b2e27" - - "0xb8f534205d4b218cd717bddf06a06cae872d995a165506cff2e8c12af6a71afa" - - "0xa93353e7b0d909e8d19d5d6c466cae15e8e5f2b929758e88d8c35f6ac180c0c2" - - "0x888daeb2cf145994cbd3f25806c306082dd402d4b4420f0817c5a5a19132cb36" - deposit_root: "0x334dbf7b9b0cd5d22f79c7dca388d3baf541e430bdd31883fd7402852febf29a" - deposit_count: 303 - execution_block_hash: "0x42bc399b54a8e566243c3ba4e2e1ceeca8eea2b7cd84462b8814229d9f459796" - execution_block_height: 304 -- deposit_data: - pubkey: "0x810ab821a4d4a0707e0f70eb7dbf78a528e2503749e462fda69969ef1aeee9ad2b8d37c77056bdb0feecdf206313d2e9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8899dcaccd1c167c69c6e4d5ab42f03f5a6e9bf1a1d664dbf07fac6de06b59e651147cf78a2924acedb33944a8d11d451189e78c32a646260d0401ebe79dda5c35d00688cc0d024a2f12248b47fe5d4a7a04fdd420aca97e85f86497998f66fc" - deposit_data_root: "0x0c6eefdd540468d0e9e72aa9c1394441e05788e7f0998cf6f5157466fdde05d2" - eth1_data: - deposit_root: "0x3fddb8ccad286ee5c85d0e2d851ee3d45dd7b03bef1a980c60ba49166465af93" - deposit_count: "304" - block_hash: "0xb0338e285f4f8d5621dfb6894d21095a58cc89d5d8b0e574edf8efe7ca30ad26" - block_height: 305 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - deposit_root: "0x3fddb8ccad286ee5c85d0e2d851ee3d45dd7b03bef1a980c60ba49166465af93" - deposit_count: 304 - execution_block_hash: "0xb0338e285f4f8d5621dfb6894d21095a58cc89d5d8b0e574edf8efe7ca30ad26" - execution_block_height: 305 -- deposit_data: - pubkey: "0x80f23973b414503e2ba0a53f67463f332b23fe5fd5466f1a770a6d548fdf4421d9370ae890b54be1cbeacb7a9e31d166" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x85a60518498341146050685aefe04587f4f9cbb7210bee705e2018c351d358d357330ca0a81da886788228eb0a3688d202024a72a5c378ccbba3c7fc7fbfcf84d630c0a8cac56ee029b8348d83b6b73b1e7019dadd34ed0272e7e6f0ea8b36b5" - deposit_data_root: "0x7562d64a4e031ff5e46a3319de670de403d8b14c256ade2aa6c3f69b2e0ae077" - eth1_data: - deposit_root: "0x567a327092d5109af0a5b29ad3c445d7444dd00fa1e34b678db5e04b48a54917" - deposit_count: "305" - block_hash: "0x30f7bf7c4b84b1e0e14234732b33fd5eaa755838404fd7a9558a0d52b29a072d" - block_height: 306 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0x7562d64a4e031ff5e46a3319de670de403d8b14c256ade2aa6c3f69b2e0ae077" - deposit_root: "0x567a327092d5109af0a5b29ad3c445d7444dd00fa1e34b678db5e04b48a54917" - deposit_count: 305 - execution_block_hash: "0x30f7bf7c4b84b1e0e14234732b33fd5eaa755838404fd7a9558a0d52b29a072d" - execution_block_height: 306 -- deposit_data: - pubkey: "0x95ce181db92ea8695c006896d2bb9faada9e157a896cf06b5466c304ccc6b09f7b8b10044b9b0689265ee97a6388dcd7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5911e4df04e991fe79576339db9eabcdaa7a691fcf6a07e86666b81f4176fb2cf7bdcd93f7512f3d8a0bef4725c30680e6ee5439ac60e013ea0cdfc8c11c0a044f0a64c7393bd12927b9c51b45b2862311ca1169fcd83b798c1653d1131d98d" - deposit_data_root: "0x88d50a861161dc2f5a2fd41cc69eca91e1e251d29224d3e813937860204f93f8" - eth1_data: - deposit_root: "0x3e8177de3da30a03e473faa90d01ef262563526c5a28c17d22c4bba44430eef5" - deposit_count: "306" - block_hash: "0x0168559f9953fd501450a3ff14cd7b48b0b75663a319abd9766d4d9fc514a2e0" - block_height: 307 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0x2685dc940c27a8289d56e5cf4246afc27ad50baf0abef93caa8e379dcb39b41e" - deposit_root: "0x3e8177de3da30a03e473faa90d01ef262563526c5a28c17d22c4bba44430eef5" - deposit_count: 306 - execution_block_hash: "0x0168559f9953fd501450a3ff14cd7b48b0b75663a319abd9766d4d9fc514a2e0" - execution_block_height: 307 -- deposit_data: - pubkey: "0x8e322c2995e2472a7981466eb4b890d753b2016798cf640ed4b7ea7a3d53e5644361f404c5eec4baa1a55da860b7981c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaeb741e937c13a7f8dfa2b7603c902719786f247a25b0eedf6327921d09486afb8ef66c30b232d6032d4ab5475dd54f407c702a09cad1bfd675ff57f44d100a996af265c6c275c69f0593da7832a81cad4417b82fbfc6a088482b3aa1d856a4f" - deposit_data_root: "0xc275ba69111f046f57da439d9b3d3e52a7124328b5909b40655e1afe75bfbbf1" - eth1_data: - deposit_root: "0xf228d693edc9dde11a393ab4191a7b47ad4eabe0839bba4fcdc6a63cda49f729" - deposit_count: "307" - block_hash: "0x6d3facdb3a92f45bf9f2d65576346739d524e703d4057c1d8474b3fe9de8667f" - block_height: 308 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0x2685dc940c27a8289d56e5cf4246afc27ad50baf0abef93caa8e379dcb39b41e" - - "0xc275ba69111f046f57da439d9b3d3e52a7124328b5909b40655e1afe75bfbbf1" - deposit_root: "0xf228d693edc9dde11a393ab4191a7b47ad4eabe0839bba4fcdc6a63cda49f729" - deposit_count: 307 - execution_block_hash: "0x6d3facdb3a92f45bf9f2d65576346739d524e703d4057c1d8474b3fe9de8667f" - execution_block_height: 308 -- deposit_data: - pubkey: "0xb6f68789c60d1348b5e24bd6b7ccc18500be7b6ef0099895547a83f63eca37ca4f26fa3517d5bc71bc4a2fb186149179" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8888f11eaefbd7bee2d93a8713415d0452b6cdf794f5095a7f4b7913556b17e969d6b1137048e2fa94af582d4fea848e185cd3df47f5071d49ec86d21389443e4771f31fc466c3bdf211cca260f677769c90d5493b6a0e8a230c455a9f6539cd" - deposit_data_root: "0xc5256c0995975cc0953677c36413969b40e96b3921007fd975bdb7d5cc883d5c" - eth1_data: - deposit_root: "0x301dd53110abda16d11eee1d46ccbc839f8e9adc1c0f0592aa010242f123397f" - deposit_count: "308" - block_hash: "0x4c2a9c39824275f99a4ef57cff7778b6fb793cd493d7e96da426f9f5c50b19f0" - block_height: 309 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0x428b54bb40a87f83657cfb7998905b4deaad55f2d092f13b0365b11847bd984b" - deposit_root: "0x301dd53110abda16d11eee1d46ccbc839f8e9adc1c0f0592aa010242f123397f" - deposit_count: 308 - execution_block_hash: "0x4c2a9c39824275f99a4ef57cff7778b6fb793cd493d7e96da426f9f5c50b19f0" - execution_block_height: 309 -- deposit_data: - pubkey: "0xb50d3fc8e36d2f2eb544c16b959b500160c2b176298e4e16ae5d928856406c86605b8ea6bd802ca1a2d1c9210d6bce61" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa88f8ea391dcba3fbbf6a4ffb58bcb8135d0001207c3656f671fab5669903a3d1fbf1150e856abd0c2d180f5518205d707692aa6a239ab2bea39338b11b0a04cb078bc61fe966feb25b905b1629067a6107bb858393339002119592d03d60395" - deposit_data_root: "0x768bb277eec0d74fe504242f61cd37bd63a76417f860ea154d89bf1e9209318e" - eth1_data: - deposit_root: "0x03403e71434239fc6cf3c51e014029b6dce79899d189cb789ca41ec8c76870df" - deposit_count: "309" - block_hash: "0x142755a334e7b0f2ecd19714918edfc8fc49e591dbe27a072f98b929914795c2" - block_height: 310 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0x428b54bb40a87f83657cfb7998905b4deaad55f2d092f13b0365b11847bd984b" - - "0x768bb277eec0d74fe504242f61cd37bd63a76417f860ea154d89bf1e9209318e" - deposit_root: "0x03403e71434239fc6cf3c51e014029b6dce79899d189cb789ca41ec8c76870df" - deposit_count: 309 - execution_block_hash: "0x142755a334e7b0f2ecd19714918edfc8fc49e591dbe27a072f98b929914795c2" - execution_block_height: 310 -- deposit_data: - pubkey: "0xa72612ca8f454d74489512d0637191bcac72124f4111f7cd52de8920ee3ea804ee895201ed74527407159220ed00712b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x988ee0d14e460e614b4db31aa0edce326450745ae33898a67f59279ca125a60a420e3fef3440be1502b173cf3b6cf75f16e427739cc02e79804a5533453b752962eb695656c5f7eadb3c13588762fcc743978bd3d6ccd4feeadedbb9bf2b989d" - deposit_data_root: "0x0275c66e7493040447a4a1ce248051418acdc6c62cbe2e5de0a6ab4737f1c6c7" - eth1_data: - deposit_root: "0x80a4bb6bffb48e9c359242c260546b21e0b52020788e5a30a6c06a4ac8728d4f" - deposit_count: "310" - block_hash: "0x626aa2fd2714dfff5d15b35def25420116d8ccb50a602513f9596ed98e06040b" - block_height: 311 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0x428b54bb40a87f83657cfb7998905b4deaad55f2d092f13b0365b11847bd984b" - - "0xeb3f38ea9f413e0b73256ebd08ed8be2b639aced165911e48656e44330d03d5a" - deposit_root: "0x80a4bb6bffb48e9c359242c260546b21e0b52020788e5a30a6c06a4ac8728d4f" - deposit_count: 310 - execution_block_hash: "0x626aa2fd2714dfff5d15b35def25420116d8ccb50a602513f9596ed98e06040b" - execution_block_height: 311 -- deposit_data: - pubkey: "0xabdcd975ccfab19f10d7ec535f7c0e9e269a34f5eeb653d15ee0505f383274e5a20b3b71bdba0f369e36cea0eae1f5b1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x80039f2345c88aeb006695982d70543e37c8cf5a1622b589326d215ff2687396eba8797e896c590878aff20bcdfeecdb0e4e9534df8429e3cc8c585d0a1821c9c9378d4f2335f4dacb7d59794539c7d77306da1f065a126593bfcce78465d8e9" - deposit_data_root: "0x12463230892e22766c8e4be4bde2f242553e7b213843ef3cb46c4ef6612cab71" - eth1_data: - deposit_root: "0xa0dffba4c6f35f9ce7e1e951a12c523651b6aa4bfb17e2323cf26324cce04330" - deposit_count: "311" - block_hash: "0xfb60531376af074a51de3a1bbceb7df9fab77acaca749ee4f779abf774252092" - block_height: 312 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0x428b54bb40a87f83657cfb7998905b4deaad55f2d092f13b0365b11847bd984b" - - "0xeb3f38ea9f413e0b73256ebd08ed8be2b639aced165911e48656e44330d03d5a" - - "0x12463230892e22766c8e4be4bde2f242553e7b213843ef3cb46c4ef6612cab71" - deposit_root: "0xa0dffba4c6f35f9ce7e1e951a12c523651b6aa4bfb17e2323cf26324cce04330" - deposit_count: 311 - execution_block_hash: "0xfb60531376af074a51de3a1bbceb7df9fab77acaca749ee4f779abf774252092" - execution_block_height: 312 -- deposit_data: - pubkey: "0x83dbae1b08eea5bf0a89eb1d0b61e9e0c4e1142610ac8d88d9f3bdef550e3264b54bd47052d481738b92ffbcf6e36f67" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xabec6694b693a184bc5720f1d0c4d8dd8630609076af52ae7fa62c04fa7df7cc3c793d1eb776199a8755e861c9bd35410c3e225f31912cd2fb21694fcf444db989aa5e4eacc3434ac4ecb894fd4f203f1514e3034e328cf5d174ee896be4e26e" - deposit_data_root: "0x627fbf478b052d8b09b47b17598cb372ef316a9e2df9fee7e04639f9f23a9060" - eth1_data: - deposit_root: "0x5c072d54c707ccbcd880507073f57cc01b6f162aea0a100c4523deb9508f357a" - deposit_count: "312" - block_hash: "0x4a09a6f7522d4793a3c0f56fd21714035a19d37f18d975a218b34952df576c9b" - block_height: 313 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - deposit_root: "0x5c072d54c707ccbcd880507073f57cc01b6f162aea0a100c4523deb9508f357a" - deposit_count: 312 - execution_block_hash: "0x4a09a6f7522d4793a3c0f56fd21714035a19d37f18d975a218b34952df576c9b" - execution_block_height: 313 -- deposit_data: - pubkey: "0xb628c432d9d76dd6483ed50938fc4c369bd24b6e3c1a41cc4620f7b513148fb242a76c7577f8766a14770ae597c36aa4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x90be83d06747ca26fd1d38a4847a9fbb02f2433ba940b51f364ca9bacb3924a2ffbc0d6e23366eeccfafaed746e29bce12589c80fb49db1b11df5da16a75aa4605b571efd142c59c07e0607cbbf6c16d989461a515e2a44b4f3c4c561fba0505" - deposit_data_root: "0x74cc9ddae3f91c1b4bd274ed0ed14c29daaba30aed1632521ef60ca123ce2d09" - eth1_data: - deposit_root: "0x7789482c357af66154948684754c641d23d0d2556cbe3f017a9680aabb46934e" - deposit_count: "313" - block_hash: "0xad05a4ad4a1dd8968ce13df5c3a6fed1f0899849372b26285fc4288bab0e2b65" - block_height: 314 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - - "0x74cc9ddae3f91c1b4bd274ed0ed14c29daaba30aed1632521ef60ca123ce2d09" - deposit_root: "0x7789482c357af66154948684754c641d23d0d2556cbe3f017a9680aabb46934e" - deposit_count: 313 - execution_block_hash: "0xad05a4ad4a1dd8968ce13df5c3a6fed1f0899849372b26285fc4288bab0e2b65" - execution_block_height: 314 -- deposit_data: - pubkey: "0xa36425294d9fe4f803acb3ce90947ea5f20cb1c06c4899b80129d8fd7e491f0128d86f98aa987a1578ec1244ae3d5f17" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb19797920d1e40e85dafd881d1193e1b347dc252974d55d0187abfd6e0f2a195746fc24527529581c1f59f3d3bea4d2d0ef3080d515468c273d216711cb3b618d9f7f25f7da3ef789d060cb1811a747a3ec0ab93c149bb8656825451426dc668" - deposit_data_root: "0x955a4d69a457b02106c01614769dfb29fe7d5c6672da037a22e16619edb5c06e" - eth1_data: - deposit_root: "0x627b60a6d46949a3c8dfde03b024118cf57f0b8e3590ab48c2523715e48b9011" - deposit_count: "314" - block_hash: "0x2ddf1d0ff89d425c64c78796ae3826cd030caa6ae70fbaa29c24a0d81abcdfb3" - block_height: 315 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - - "0x69106b127afcf2e4ddba6429779d04030f20a0ae7eab086bb65958c789430cd4" - deposit_root: "0x627b60a6d46949a3c8dfde03b024118cf57f0b8e3590ab48c2523715e48b9011" - deposit_count: 314 - execution_block_hash: "0x2ddf1d0ff89d425c64c78796ae3826cd030caa6ae70fbaa29c24a0d81abcdfb3" - execution_block_height: 315 -- deposit_data: - pubkey: "0xac55318cef8cd8f015f2493d09be7556066e235f618971a3325368f3deba029bae60fba85171762827f1f70c6883a467" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b4f261038c4bff210d1a0925059e46cd0d830b16b6a20da2a8ac9bdde1239f15b21d20550fd5d1878977bf0e0358fec155a9cbd51112d9ecaabaa5d3a56100afe4f18a3a0ade34698d2e84e52e164fe83c4b1140ad4a6d048a09e658c57f8b0" - deposit_data_root: "0x7dbeb694af44e130ec3a9390893a798e86e787ed494bacfc10a3ef49d8e85889" - eth1_data: - deposit_root: "0xc8cc34185cb292295e96bbb5ab092b69eacb7984aebe7f0d88ae077aa6c71e20" - deposit_count: "315" - block_hash: "0x48396fede67f0d87fdb841ae3f8dd588552fc1b43141ff5c2dae2f143a305a65" - block_height: 316 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - - "0x69106b127afcf2e4ddba6429779d04030f20a0ae7eab086bb65958c789430cd4" - - "0x7dbeb694af44e130ec3a9390893a798e86e787ed494bacfc10a3ef49d8e85889" - deposit_root: "0xc8cc34185cb292295e96bbb5ab092b69eacb7984aebe7f0d88ae077aa6c71e20" - deposit_count: 315 - execution_block_hash: "0x48396fede67f0d87fdb841ae3f8dd588552fc1b43141ff5c2dae2f143a305a65" - execution_block_height: 316 -- deposit_data: - pubkey: "0x9966dc1de264da86652de12f70b4d6c7271026be0b081797589ba126108809b6264e2ec18cd59e90dd2c639bcd9986c2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x97dd0583026f23df048ceccb8815d9ae93a5a61cc8ae0e57ef7bee0e0c16f146ba1e0b31c5490f61697c83dd8eabab7f025d724669389df5fcf1abc54f0d7715885d5db3c0f2dda0ecf1ec51fcbf613a9531894f9bec308782b73441a1f29ad3" - deposit_data_root: "0x0f9245219098e059529bd695cb6a8409d6a7b771146e799ea918c4bc4778aaac" - eth1_data: - deposit_root: "0xd275e61f8ffd78a24684a3e46425d38e6dffa2babf29bbf98d3521ef0d6891e4" - deposit_count: "316" - block_hash: "0xb4bd7e404339b1e80030772dc8b8f1721d29b9b31516b26eb094eac84d8f36d1" - block_height: 317 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - - "0x587c330aecf776f443ed580dc13abd4d0a36e4f8617e25366dce8a28e796d9e2" - deposit_root: "0xd275e61f8ffd78a24684a3e46425d38e6dffa2babf29bbf98d3521ef0d6891e4" - deposit_count: 316 - execution_block_hash: "0xb4bd7e404339b1e80030772dc8b8f1721d29b9b31516b26eb094eac84d8f36d1" - execution_block_height: 317 -- deposit_data: - pubkey: "0xb5a83ace1a9e683361435bb484295c11e371316800c073c8beaf1ee1900d5589966ac8266ee11b10b24f671231584302" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9029f10262639dc7a78130c1e0d924531a0939ce02c93df4171d9bda8a055dd270588fb54bc87c0a19a8ae1a3b93f02d1471907debb2c43e052a2bc6c71dc80751c96e46ecdb6e64ee5dbd86669b821e4fb7ef1a863af73a25207391e92c2f15" - deposit_data_root: "0x2eea6ad287847510e3abaacdd5d8b21bf4c3cb317cee848b5150d76c7eee2326" - eth1_data: - deposit_root: "0xf7814cef15936b64805475bca92a6a37105312c8fdb4b6088bbf8d02aaf03a78" - deposit_count: "317" - block_hash: "0xb1baae047525e7438d4e06c776b9c14549a2bb0539275ce2d43a54605802105a" - block_height: 318 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - - "0x587c330aecf776f443ed580dc13abd4d0a36e4f8617e25366dce8a28e796d9e2" - - "0x2eea6ad287847510e3abaacdd5d8b21bf4c3cb317cee848b5150d76c7eee2326" - deposit_root: "0xf7814cef15936b64805475bca92a6a37105312c8fdb4b6088bbf8d02aaf03a78" - deposit_count: 317 - execution_block_hash: "0xb1baae047525e7438d4e06c776b9c14549a2bb0539275ce2d43a54605802105a" - execution_block_height: 318 -- deposit_data: - pubkey: "0x8428f1acb3bf75a11ca3ff7b9fcb2af1f0c43ae22f08d4ceb586c96f97f5d999273739d348900e2424cd375e5ef68f35" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb7a7faad5149b6e8b901414c37b01b5955033c7b7b29aab827b29cdd0a7bb32bcaebe1c67bdc1de99a972cfa52defdf518084a465c3804241e9d3915b441a8a108770f557e5b372ec2f4d7bb127b03585ce8ea85ab35506c51fbdd91ac109403" - deposit_data_root: "0x37f7ed384f323053073d09ba424f7fcba44ae6b1f50464f210cf880df9ec4e8c" - eth1_data: - deposit_root: "0xf1b373962ed896fb473e2e0625acad818126834046cd4f36d43ef3c3fe15a589" - deposit_count: "318" - block_hash: "0xe756598b26a44670dfd997e1be73332113f9605d5d8d7f07ee3162f04d72ccd1" - block_height: 319 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - - "0x587c330aecf776f443ed580dc13abd4d0a36e4f8617e25366dce8a28e796d9e2" - - "0xfef52200f08b898e7c1909fe36e281e60093f925aa8400431e6f2fad81571ce1" - deposit_root: "0xf1b373962ed896fb473e2e0625acad818126834046cd4f36d43ef3c3fe15a589" - deposit_count: 318 - execution_block_hash: "0xe756598b26a44670dfd997e1be73332113f9605d5d8d7f07ee3162f04d72ccd1" - execution_block_height: 319 -- deposit_data: - pubkey: "0x8e73eafaa8fb6863473e55ef578c4764d9330996a62132a9329ab43f2a45ef2e68637cf48f674c8a0d127d9fb2738229" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x93dd39078da197a9ed00edf9ddff70017afad6b5cf9c9c3eb139bd61b362152c6d1d2bfce66fb93445eef99d2ddbbd041371aa6178eeb511229633be0e31fd692628a383b42e11a3e8a219c66bf15942c34658c1cffe86b97857145b0cf14f8d" - deposit_data_root: "0xaa707e0428d7847f2f87e9dd478d34b093aa50c6bd025d7152f7fc48b5e61f5f" - eth1_data: - deposit_root: "0x24873485c0d733699f5309721201310f38e8e8a3fdd5c63dcab7f918a360e9a7" - deposit_count: "319" - block_hash: "0x703e8869ebd982b635b6f7f298cddff7ee58d5f8d9518c80c0d5aa0cd5bea3df" - block_height: 320 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0x22c8f83b80ba6d15c5d143935e2decb02783c33299c81a81495369e44fd7f121" - - "0xd332180e48743692efc13e599dedfd063f73963ababcf12ee1dbcddab376bcd5" - - "0xb1125c174cf98e6ea57e02bfb8108d15da2cc5c7f1d49167551a9bf8bc37a7df" - - "0x587c330aecf776f443ed580dc13abd4d0a36e4f8617e25366dce8a28e796d9e2" - - "0xfef52200f08b898e7c1909fe36e281e60093f925aa8400431e6f2fad81571ce1" - - "0xaa707e0428d7847f2f87e9dd478d34b093aa50c6bd025d7152f7fc48b5e61f5f" - deposit_root: "0x24873485c0d733699f5309721201310f38e8e8a3fdd5c63dcab7f918a360e9a7" - deposit_count: 319 - execution_block_hash: "0x703e8869ebd982b635b6f7f298cddff7ee58d5f8d9518c80c0d5aa0cd5bea3df" - execution_block_height: 320 -- deposit_data: - pubkey: "0xae451c6d18b7a14585dc96423f078be6b46742dde9be4d52907fff934956ebb2332e82c66cc2354eeb2da821e80ab7bb" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89bb72ea77d8cf12f08050fe005f9da2e6d31b5348ec1e6e7b46e1084664c5b2f486c7c44773df37ecbdf1ab7c3f86e00fc35ebd2f4d470d929deda82847570be28c6191631235ce4901ea75920638210cdc92f2776889addfefdc77023ec196" - deposit_data_root: "0x7407798bee346eb5f23e00cdc0fbf7fddfbc35a0ab4c6573296cc0d58fe5dbb3" - eth1_data: - deposit_root: "0x2b537afcf4a0b123429379355ab08f2ed8e1f5a15311d37e6dfdb7436d78bf87" - deposit_count: "320" - block_hash: "0x4e38625114a763d73cb04258b94c92eda5b6eee0507e18e9f4b562cbfb2060e6" - block_height: 321 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - deposit_root: "0x2b537afcf4a0b123429379355ab08f2ed8e1f5a15311d37e6dfdb7436d78bf87" - deposit_count: 320 - execution_block_hash: "0x4e38625114a763d73cb04258b94c92eda5b6eee0507e18e9f4b562cbfb2060e6" - execution_block_height: 321 -- deposit_data: - pubkey: "0x953ecb430dfe4f91510dbb8166f07a557881c19112e41a8b1b8b0aa2c3d95c061cdff951044255a3e4df6f529302beff" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x90650885698e776deaeb845e2582dc5a60770c20f08563c429ee2e6657d01e4ae4e0a6464e9669341f3b04f64846a02f05b00b950fb4c5b10363582bce2ac313ec4534758bc293b3b9eab0149217e45b761952b6a894e3f42226d9392d33219a" - deposit_data_root: "0xc978fe6c6f919724950edef3008541909ddc2965bf9bdd297aa98ff6760919c9" - eth1_data: - deposit_root: "0x0d9593241269b53b9aab5168a1db67f2ac6afb5e1811ca34627e5f510fa47c27" - deposit_count: "321" - block_hash: "0x101e4774d651227e02db01e67dd883d7ecfa1762fc5773639bdec3394fec49b8" - block_height: 322 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0xc978fe6c6f919724950edef3008541909ddc2965bf9bdd297aa98ff6760919c9" - deposit_root: "0x0d9593241269b53b9aab5168a1db67f2ac6afb5e1811ca34627e5f510fa47c27" - deposit_count: 321 - execution_block_hash: "0x101e4774d651227e02db01e67dd883d7ecfa1762fc5773639bdec3394fec49b8" - execution_block_height: 322 -- deposit_data: - pubkey: "0xae629cdd3ee2278a8993feb4e163a9127c4a09857981704fad31e3fbf2e7668136f2943987c7b5619d7e362d1bf9d9ee" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4700ecbc23a35301973959072651da1963b1ec856c7d210e4bf165d7a306c501b9e31fa34e73b4001a1ab3a9b0acdaa0c62ba65f79e3ccfeebee512fdf980dc937a1c34a59bf67f323de0753ffc974ac8047948d924cd9204827b0587fef031" - deposit_data_root: "0xa35ac1d1af9eab97f2679f3dfaef4a934ab6de575cadb245a45d754ff1c696df" - eth1_data: - deposit_root: "0xe3ab2d88fbc1cf4b39a86b71ea1d020c2d8231c69cc6d6eb2523c08b7fba9426" - deposit_count: "322" - block_hash: "0xc581458176fa41f0363a1ca199b1926a761489bf6dfacfa6cecbecffac82995a" - block_height: 323 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x909fdc4a762e835a5b890dc4b1c41ca41b65df75770ac25537840d618375a2b5" - deposit_root: "0xe3ab2d88fbc1cf4b39a86b71ea1d020c2d8231c69cc6d6eb2523c08b7fba9426" - deposit_count: 322 - execution_block_hash: "0xc581458176fa41f0363a1ca199b1926a761489bf6dfacfa6cecbecffac82995a" - execution_block_height: 323 -- deposit_data: - pubkey: "0x963564994c2f8935ab03230c47dbd3596ca372a892a616c28ed80a582b9ca4a43e6ef58cd2fb2a3f3c03ac02eb272a82" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa470adad0a49fb8d863a31f7a942901ad76014898fb519d0df83215bf4df02db3348d42ffd4dd12c8e2ac0b421994e1b00325ccecfdc0ee8319fa5c135df27fdca2295568890a024178ec4c4b60fe9354c384792f0e61c8949461c8f0f211d19" - deposit_data_root: "0x2aa0578a29fb670faca20939e663b953e70177df3cc91ba85d38332b4178b9d7" - eth1_data: - deposit_root: "0xaedca7ce962cb7ef634eae179f6f58c38f2dc6ea6121c41cdda7ec9438a27514" - deposit_count: "323" - block_hash: "0xfa06fa004648cc64acc6bd93a8a309d9494409f073f19d07d8a8221fbcc49635" - block_height: 324 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x909fdc4a762e835a5b890dc4b1c41ca41b65df75770ac25537840d618375a2b5" - - "0x2aa0578a29fb670faca20939e663b953e70177df3cc91ba85d38332b4178b9d7" - deposit_root: "0xaedca7ce962cb7ef634eae179f6f58c38f2dc6ea6121c41cdda7ec9438a27514" - deposit_count: 323 - execution_block_hash: "0xfa06fa004648cc64acc6bd93a8a309d9494409f073f19d07d8a8221fbcc49635" - execution_block_height: 324 -- deposit_data: - pubkey: "0x893f6809ed7bfd91951ba78b29c2eed9e8147ed63f01749380a0b6bba9bfd3c7654f05d2aa77fb7a096b981745615055" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaffa69acb9ec862c2e6a48e490ac305a1f7b9ea17b95c5aa416bb4f8f7682abcb8fd9f1074f989f99b780eeb8ba2f03015f8901fb0b0c2c90570452da90c0ecf591a739d6bf8e6e1ec13ce9d4644a4517e44a8bc923d1688732af77cf4e3a2bd" - deposit_data_root: "0x5b62379e4f53e1806f256162f584ccac600b1b11d21ea858bcd61d8cea1b168b" - eth1_data: - deposit_root: "0x637c3e9b84ad5037a6e90a895f20d1b5ec49a3266ee9e3b3c4c632ce3f3f012b" - deposit_count: "324" - block_hash: "0x104902e1b47f88b3220f39f473f2f47d0e5b8f12041e12b6f1796ddaf70b962c" - block_height: 325 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x829bab24c40bf37b3bed73fa4dd76eca6f03f8cdc0d73ec6decd58c56626fdca" - deposit_root: "0x637c3e9b84ad5037a6e90a895f20d1b5ec49a3266ee9e3b3c4c632ce3f3f012b" - deposit_count: 324 - execution_block_hash: "0x104902e1b47f88b3220f39f473f2f47d0e5b8f12041e12b6f1796ddaf70b962c" - execution_block_height: 325 -- deposit_data: - pubkey: "0x868207372934c6e8133df3c6a1d4a993cca26a06616d8f3de0c593a4cca3005d6e1a73b41620b05a31eb44841fc20932" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8485459f5c442443a0075bce597ec6428477bc1f2634fe463397f35b39fab15fe876f1d9c22dc0a9eea20a546f92c39f175ff8402decdffda89f03d074d0c6abd9b95ead19f2b015e9cd6637c505131a38f94c0cc1f39e79152ab5fded97d295" - deposit_data_root: "0xc19bad7ae315d2679d0a9a44b416955031393874ad7d4f86924ab2e7412dd86c" - eth1_data: - deposit_root: "0xd7162fb64e8276d1d722de19732d3d13cc63963606dd5afd722c901721b001ad" - deposit_count: "325" - block_hash: "0xf56c5c31b8721f20db972d51c8dc999755b33f49f051f8ecc343a6ce30ba1695" - block_height: 326 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x829bab24c40bf37b3bed73fa4dd76eca6f03f8cdc0d73ec6decd58c56626fdca" - - "0xc19bad7ae315d2679d0a9a44b416955031393874ad7d4f86924ab2e7412dd86c" - deposit_root: "0xd7162fb64e8276d1d722de19732d3d13cc63963606dd5afd722c901721b001ad" - deposit_count: 325 - execution_block_hash: "0xf56c5c31b8721f20db972d51c8dc999755b33f49f051f8ecc343a6ce30ba1695" - execution_block_height: 326 -- deposit_data: - pubkey: "0xafa8898fca81793be528c433c00c9838be077b05b7fee5c82c2ff1bea10834f3aae64e2d5b8e3cb3e4a8104127f2b99e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83536ec416710222ad12669bf77c64062b11d01bbf986d900efb37174e45673764941a630a641d4971bcc1ef031bf6110f7f65599ae492f7e2581af7fbe532aae11882007c3f89736eb7c840b21d194f2805e4016b0cb554255ca59343d741ea" - deposit_data_root: "0x119dec41ef12a9e3fb28da1a750b117e63d1d0f59a65668984336eec709b8061" - eth1_data: - deposit_root: "0xa6f2b846a99b787bfd81acf69ffafca4811ab7c97ba50fbd2ddcf2dc508cd5b9" - deposit_count: "326" - block_hash: "0x5d0b737eb0d181195028dd01961a295c8e2baaf0d9aefd647bf884032089245e" - block_height: 327 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x829bab24c40bf37b3bed73fa4dd76eca6f03f8cdc0d73ec6decd58c56626fdca" - - "0x6352a5698a39b09b04e0d89ee74b46a579de3e4176cb60ec11c6aad278f2e0ce" - deposit_root: "0xa6f2b846a99b787bfd81acf69ffafca4811ab7c97ba50fbd2ddcf2dc508cd5b9" - deposit_count: 326 - execution_block_hash: "0x5d0b737eb0d181195028dd01961a295c8e2baaf0d9aefd647bf884032089245e" - execution_block_height: 327 -- deposit_data: - pubkey: "0x9685e2fb1018a705d32745ac6bd64d7bc5861679753d5f886a9439c1f8f85cc247b392035fcae82233aa39a6be24515a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8d766fcb28fe1c2c80c93cfb974f700b2b413dc9abcf0621f623fc8f7059e7d2ede209c75a426c7cbc264a08435790831645cbf7f6f977d9dde8e1382db0ca6f8d7c153e47c7f9a2bd5afc1c09b2d5a0d60b4c7357fad005c53d6e29ed369d57" - deposit_data_root: "0xd469915f8ff96a6b4944ac2cfb6cfaf5086b4502819e279ddc3d079da18351a9" - eth1_data: - deposit_root: "0xefe013b8db2bc21b289a80f7eac2d726307ff64d7182c92d3c38f8bf5b070ed3" - deposit_count: "327" - block_hash: "0x7e26828262c78521add66b854b9f6202f38c1b9425f93b7bbe786da0ca6a9d41" - block_height: 328 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x829bab24c40bf37b3bed73fa4dd76eca6f03f8cdc0d73ec6decd58c56626fdca" - - "0x6352a5698a39b09b04e0d89ee74b46a579de3e4176cb60ec11c6aad278f2e0ce" - - "0xd469915f8ff96a6b4944ac2cfb6cfaf5086b4502819e279ddc3d079da18351a9" - deposit_root: "0xefe013b8db2bc21b289a80f7eac2d726307ff64d7182c92d3c38f8bf5b070ed3" - deposit_count: 327 - execution_block_hash: "0x7e26828262c78521add66b854b9f6202f38c1b9425f93b7bbe786da0ca6a9d41" - execution_block_height: 328 -- deposit_data: - pubkey: "0xb41c965012fdbf74f551e0a1372202f5814cedfe541336f5954020ee2ef23043efa6baa200cbd22b34acc5462ca3e9cd" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x86db0292465a7a813f4e6da0d9dc4df20852b088a096b5b569251f544f4e98d2e3125926f1ed97fbcddc356de8461ca1173e9466d181b5f7fc63f772313bc3bb5e37b681c845b5cec8954762565b0614c0413da52147e461bda26ac718afd166" - deposit_data_root: "0x2a5c7ed2c0ef2938c1edeaa39bbe965c3b7ddfbf544553892c02c2b502c19f33" - eth1_data: - deposit_root: "0xa7a049ceb8533aaaee37c8540f1ad14e400ce325de95ef3ff6d9a5a30277aa80" - deposit_count: "328" - block_hash: "0x7368b2a4ec10255ec111a7b79ef69efee937e637da93706c68907df2eb5ec722" - block_height: 329 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - deposit_root: "0xa7a049ceb8533aaaee37c8540f1ad14e400ce325de95ef3ff6d9a5a30277aa80" - deposit_count: 328 - execution_block_hash: "0x7368b2a4ec10255ec111a7b79ef69efee937e637da93706c68907df2eb5ec722" - execution_block_height: 329 -- deposit_data: - pubkey: "0xa8a7015ac1ac3ec6678478167302dade9eedced09f899a60decc8060f972141a88be910f959aa155cceea322f375ae72" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa3b80ecbaf373ff93624739d4fe040d037ebcf3ef906b294b3b908325547aa217b930b0663b2d9589aab52084a028b370c455a452f4ca2ac2156bc7f4283f96c1b3a770d05d9570dfd57844142aa167091359e2a2364de250d22ba06533e4736" - deposit_data_root: "0x59397bf83c3fc9c58074f1e8139affd9df65b46dd060f498562079145f499f34" - eth1_data: - deposit_root: "0x638a7dd7929e0e9ce2efd35226d44a66c555379ddb234e778244064ed44fb3a1" - deposit_count: "329" - block_hash: "0x32438de8960d5072d117ed3497e8c3ae169df2c634103b6e708bc04bce0de62c" - block_height: 330 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - - "0x59397bf83c3fc9c58074f1e8139affd9df65b46dd060f498562079145f499f34" - deposit_root: "0x638a7dd7929e0e9ce2efd35226d44a66c555379ddb234e778244064ed44fb3a1" - deposit_count: 329 - execution_block_hash: "0x32438de8960d5072d117ed3497e8c3ae169df2c634103b6e708bc04bce0de62c" - execution_block_height: 330 -- deposit_data: - pubkey: "0x83a7860b2ff27518453860637b50ed63adfbda9a2f36432e949268cdbd5a6cc10985acf9bbb57391393823d71328c85a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x973582f2e9868c13e6f8f5bf798e2111c785281e0659e1adce01524406daaa5d2da5a70c141e84da6891941b632a22af157d1239c5d8a03dbdea8157d7c3f86cd0b316ce06150e18986aca9c84740b1a389bd60234e8f95c9a3d5bcd7f4ca6b1" - deposit_data_root: "0x249592161dbff13774225e1fb6d8a41c4bdc86213ff6ad2fd5bfaa0b4c9b814d" - eth1_data: - deposit_root: "0x2676fba7f7791a8dc588e76fda0a40390988f860b6fcf543d91bdaab3f889344" - deposit_count: "330" - block_hash: "0x1c1926c17a2879fb7535863b86d080bfdb32f0213e74ba6895ab8801771bb20a" - block_height: 331 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - - "0x491c63b1a2fc1989cb6be00597134cd056a4ccbcc142850c5834a24da3a58686" - deposit_root: "0x2676fba7f7791a8dc588e76fda0a40390988f860b6fcf543d91bdaab3f889344" - deposit_count: 330 - execution_block_hash: "0x1c1926c17a2879fb7535863b86d080bfdb32f0213e74ba6895ab8801771bb20a" - execution_block_height: 331 -- deposit_data: - pubkey: "0xb7458d704a414e012c62a3d19c27da6b0ed9dc77dbc1a328e736043e156eef0af395853ba915bb246a2a4178febc7ad9" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8d51031827dc04649b8825a745eb56f32168064ec6efeadbf8e6d639b21e826ef5c61f890e816bfc66e87bbf85759d240d9c5a95d602ec1ba173fca94959c3a84b164b1d7bc6098bcdead65318f9900c79f9cda30584fd450914613cd174b425" - deposit_data_root: "0x579accbbe17b59ac71d9923a7cf7b851ccfd509caeb9fccda56c538dacf1744e" - eth1_data: - deposit_root: "0x24d257c531f4a7c0979f1778496a0e422e76f120dd5e6f97af72ba9c19b30369" - deposit_count: "331" - block_hash: "0x5d1c3942268956fbe23bac62b814488414664aaeff9a047078a2790f0ccbec6d" - block_height: 332 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - - "0x491c63b1a2fc1989cb6be00597134cd056a4ccbcc142850c5834a24da3a58686" - - "0x579accbbe17b59ac71d9923a7cf7b851ccfd509caeb9fccda56c538dacf1744e" - deposit_root: "0x24d257c531f4a7c0979f1778496a0e422e76f120dd5e6f97af72ba9c19b30369" - deposit_count: 331 - execution_block_hash: "0x5d1c3942268956fbe23bac62b814488414664aaeff9a047078a2790f0ccbec6d" - execution_block_height: 332 -- deposit_data: - pubkey: "0x8a6785d9912cb5be712bf51c08da89f9a3c7942869176d1a24f114f430715064f48c35d37c1c1d1eb9b9322ecfdcedb0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa19c204bfe842f321b5eb4921bfeaaf7275ccbe56531bff038b7e534ab98b13179245d804cbfe08eaa635b1f0c9271ed09754a00cbc617ce22610a78c7a621e92be914288e5412bf2568f784273c59e3d2a529c45c72689a2817a19489fdc109" - deposit_data_root: "0x3998111d43f8546fc032329d81489551501ccd9b835b0c738866336f442f675f" - eth1_data: - deposit_root: "0xcf48923ed77d78c9655d703dc1920f4824c269dd652f2999757e589abbad0247" - deposit_count: "332" - block_hash: "0xa0bbc1be8518265a9f0c090c6a3011529e0066b978151c1751962b6ee6439fe5" - block_height: 333 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - - "0xfad232bae67341101a570b10f6192e9d46fd997037d7350c72506c8ff80f9c2e" - deposit_root: "0xcf48923ed77d78c9655d703dc1920f4824c269dd652f2999757e589abbad0247" - deposit_count: 332 - execution_block_hash: "0xa0bbc1be8518265a9f0c090c6a3011529e0066b978151c1751962b6ee6439fe5" - execution_block_height: 333 -- deposit_data: - pubkey: "0xb855cac3f2635d485914e4b25b953b066dd5942215b38677d16d3fa6aac5b7f8d0849398ef946674e88514fc83c42491" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa1a4a04d87aa7051d721623609478467070098d8ee9310292a5c98ce5c49185d2fbeb8225a964906f23b4b8d864a47dc0025a10d94953de9b4846ed342fc3e97485d278f9c286417a54eee5496b9f3c1f1e51cfd3396688931f888c241f95552" - deposit_data_root: "0x6566a16b18a8c515bff85c5cfaa9d0dd7873c39afd6a50e7655d15198fc3d37f" - eth1_data: - deposit_root: "0x601f76fb38837cf4733da9f152e37859b1be5e1d18d341ca181b87df6b56320c" - deposit_count: "333" - block_hash: "0x2b03e78da06e7a66393fa47a4713ecd2f0eb79a016ad42e2e57350e9a6c1340d" - block_height: 334 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - - "0xfad232bae67341101a570b10f6192e9d46fd997037d7350c72506c8ff80f9c2e" - - "0x6566a16b18a8c515bff85c5cfaa9d0dd7873c39afd6a50e7655d15198fc3d37f" - deposit_root: "0x601f76fb38837cf4733da9f152e37859b1be5e1d18d341ca181b87df6b56320c" - deposit_count: 333 - execution_block_hash: "0x2b03e78da06e7a66393fa47a4713ecd2f0eb79a016ad42e2e57350e9a6c1340d" - execution_block_height: 334 -- deposit_data: - pubkey: "0xae8aa6029e2318a91b3b6ff77e2792905a690d7ad0ac1f97a7887ca4891642d267b7e208f6bddf407fd8c4e10caab2a5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9077969feb76785e0c70a27f1cbbe4ddf95a0d492ded0730d542b4efd8194b44761f9af983f5169a3b04894db5ec0b620f20b332626a3a41451bd2fa7699750a7422b7c3adb2886bf009cc361b35ecea393d2e6539f16af3cc29b12adc52b007" - deposit_data_root: "0x212acb543447a951ec9bc344e0dd2816b7cf8292ed26e09341b39c463bf2c1d7" - eth1_data: - deposit_root: "0x17ad9aa2ffcb568adfee07a4ea93693926ef5e196f325c0c0b1e282433e2526e" - deposit_count: "334" - block_hash: "0xeac3f8860c586f736f0bfbb5132027e74d76b0060a0751e96ffee1d90eb57707" - block_height: 335 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - - "0xfad232bae67341101a570b10f6192e9d46fd997037d7350c72506c8ff80f9c2e" - - "0x98ad3ff4a62e454b2468ff55af20008d2d05198840dd8fa50ef9c637bf07086a" - deposit_root: "0x17ad9aa2ffcb568adfee07a4ea93693926ef5e196f325c0c0b1e282433e2526e" - deposit_count: 334 - execution_block_hash: "0xeac3f8860c586f736f0bfbb5132027e74d76b0060a0751e96ffee1d90eb57707" - execution_block_height: 335 -- deposit_data: - pubkey: "0x98a5e4ffd62c3b8eb39f6230d242efb74701f42380663af26fcbf106ad1b0f02339aa6d9eb5f2f073b01f2c85062d89c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa611b0ecfe36d86f891951236a218d1f9a64abd697b4e861b2c8849b6b9b1f23287dfdfa1c2c45ca6fec8dae2e1163d1022836eb27982c328955eff7643e031c407ed8629b8c39c8d30bf3b115d0629baa493ead81959b962e42c4ea44b69403" - deposit_data_root: "0x4b2e8ce40e43537b4a92f303966172fa604c02f1c1747d558f561c4a9915e649" - eth1_data: - deposit_root: "0x6b407936ac387168556937334d1fd4d42efc8b83e2979abc7b9440264dd81e8f" - deposit_count: "335" - block_hash: "0x642c02da32878eeec84d0c17ff498fba2173c61acf4f268b1f4d937a8261d92e" - block_height: 336 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x68037963cd0454c7fca9c176f22dd9e308d606acf005cb7b2c5c800b9d443189" - - "0xfad232bae67341101a570b10f6192e9d46fd997037d7350c72506c8ff80f9c2e" - - "0x98ad3ff4a62e454b2468ff55af20008d2d05198840dd8fa50ef9c637bf07086a" - - "0x4b2e8ce40e43537b4a92f303966172fa604c02f1c1747d558f561c4a9915e649" - deposit_root: "0x6b407936ac387168556937334d1fd4d42efc8b83e2979abc7b9440264dd81e8f" - deposit_count: 335 - execution_block_hash: "0x642c02da32878eeec84d0c17ff498fba2173c61acf4f268b1f4d937a8261d92e" - execution_block_height: 336 -- deposit_data: - pubkey: "0xa7723c37795ac53e3ebc25b9dd55af10080490e872a18724b0bd3c7fecf8ab18f7d6abd1b49bc755048a3f9b1bb136ed" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x82d29cd5f8a497b898dcf8d03b7b4e43e2ab362898d80f1b8cedfce878eda388aab81dd2249c4ea11f0359ca666ecdcf12dc23fb637063a23a42f6f0140d74040faf4cedf86ffde4d4ad31a3115a652c53e3e69b8c6041341976bba45451319b" - deposit_data_root: "0xaafd33e6d9e12cd2b3c9b01a65440dd47914a24fa5ec0a54f1a8fdc04d2a4cce" - eth1_data: - deposit_root: "0xb16b2f7f6c0d8dad74c340492d57d24b032a221ae10d86ee1525f0221fcc690f" - deposit_count: "336" - block_hash: "0x0671e4bb3a224e145648e03b323fc2034eae6d285f280842370bffc9b75211b8" - block_height: 337 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - deposit_root: "0xb16b2f7f6c0d8dad74c340492d57d24b032a221ae10d86ee1525f0221fcc690f" - deposit_count: 336 - execution_block_hash: "0x0671e4bb3a224e145648e03b323fc2034eae6d285f280842370bffc9b75211b8" - execution_block_height: 337 -- deposit_data: - pubkey: "0x865f9a725b49135bae6c011f778bb311a514a73911924e4630a5fedac29e1b4d127308ac8b0d623e7f246c668b827f35" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x98df5e77c8ea74dcc876e84a85bfb829d3f8e6349e1295daae0f7817e47d62788ef5509ef4c7cfe12ca4928c2f8708380b880cff39db85f02a3ae414529cf8ea708d745cdffaec6785a09f78e31a38b6eda8a1849c987e16a896d1fac4baa936" - deposit_data_root: "0x4c1a708c3fd48341741103c34da6335e37373ce7706ce809aaac4ac88d1a8253" - eth1_data: - deposit_root: "0x796573ea2cfd71631491eb7a44bfd33ad6d1be1b3e446549409773724d8990ff" - deposit_count: "337" - block_hash: "0x76f57ebaa44d6bf4b4edd91f646b8f872bc1386822617d4e438412858e69fd19" - block_height: 338 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x4c1a708c3fd48341741103c34da6335e37373ce7706ce809aaac4ac88d1a8253" - deposit_root: "0x796573ea2cfd71631491eb7a44bfd33ad6d1be1b3e446549409773724d8990ff" - deposit_count: 337 - execution_block_hash: "0x76f57ebaa44d6bf4b4edd91f646b8f872bc1386822617d4e438412858e69fd19" - execution_block_height: 338 -- deposit_data: - pubkey: "0xa663aaabc4aae867b7bc4feb03558f9c906be74fbd1cfa931b88191a1817c8393778ef45b5cb107cbf860e9cce540212" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b7e2a3f92460dfc0e12f13ae76c2949a08fb6407ef52ca0b5059def233c0764955401253ef03f2bed3fc6feea5a09f008969ab976cfaf7a649a930b552285f4441f0d06c0234b81ee37dbf8eac19a50c8276979ade3c7bf773249a45c477e31" - deposit_data_root: "0x1500846f0a4b55b0ed41b84c3028f1df35be19bb4f82dd1a84c6b5fde4d9dc21" - eth1_data: - deposit_root: "0x955590f03dfa34324c77e65b555d2dab80e5d4da4594deb99c7564eed667b840" - deposit_count: "338" - block_hash: "0x1d34b11fbd4c5364cd26c59d0d39dab70bb4406ab876409eb98a505f474c12ab" - block_height: 339 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x02937359af82fb5ab508a78d0a5455edc12d42abc01d457cb808ae23ffe4f3b9" - deposit_root: "0x955590f03dfa34324c77e65b555d2dab80e5d4da4594deb99c7564eed667b840" - deposit_count: 338 - execution_block_hash: "0x1d34b11fbd4c5364cd26c59d0d39dab70bb4406ab876409eb98a505f474c12ab" - execution_block_height: 339 -- deposit_data: - pubkey: "0x970d2002cd899a503a0a007d6b5cc6f8ea9fc7e7db1de06b5297daa7f365d066e69cfd179197229508bdf93161904330" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb286275d11239f69f057de9e85f5f26466f0b316dd0a904a4ea87def5aec3808f7c5379d8303666902819449fab7efab1564ee7e1fabddb6f475530fee84042b76ccff7eb8a1c4788fef17ce15aba2ecdf8aac1ba25af4f9c47bf66491dc6e4d" - deposit_data_root: "0x7fd3c53f7f63b048832cf0492598920d87f4a205dbd16e53c4bb6bc9c9c9d43e" - eth1_data: - deposit_root: "0xf75235a060b1c539b96b12e4d23245f341777cac99f556f4d686c0cf44688538" - deposit_count: "339" - block_hash: "0xaedd455c7e89005ce8a0cd34b997eb55456eb21d7dfd08b0c1978717614285af" - block_height: 340 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x02937359af82fb5ab508a78d0a5455edc12d42abc01d457cb808ae23ffe4f3b9" - - "0x7fd3c53f7f63b048832cf0492598920d87f4a205dbd16e53c4bb6bc9c9c9d43e" - deposit_root: "0xf75235a060b1c539b96b12e4d23245f341777cac99f556f4d686c0cf44688538" - deposit_count: 339 - execution_block_hash: "0xaedd455c7e89005ce8a0cd34b997eb55456eb21d7dfd08b0c1978717614285af" - execution_block_height: 340 -- deposit_data: - pubkey: "0x81fc32376cd95ab4456f645030a9ccba182da956571ee1136916fe655c8bf08f05244e204bfbc87f696472b3cf3bab83" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8e625f0c45db2539e488bd2272774e46699e690e45fd6e88fa42204d9798a24aa1ea432498172c25671d65450b2ff8d008bc9d87276592803f9568cd46e4d46e1da49db778158256fdc10d8e4dede5194cc51d684887f31a4ff2725113deff26" - deposit_data_root: "0x8d6754cda9a15e4c2b26cd6c030a76af60e0dec4e17a008f30164ed9ba746cff" - eth1_data: - deposit_root: "0xcb7ce227d926f427218766a5fca5311ec8111498381867d4fa68212857ec0e41" - deposit_count: "340" - block_hash: "0xdc73d2399512a4f010ac368d10034f9ec2ca191af40dc0be3c768a0c56bcf02b" - block_height: 341 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x691d6c3d1788448165cd972951b94cca4485afc7571864393cb986aca78a99bb" - deposit_root: "0xcb7ce227d926f427218766a5fca5311ec8111498381867d4fa68212857ec0e41" - deposit_count: 340 - execution_block_hash: "0xdc73d2399512a4f010ac368d10034f9ec2ca191af40dc0be3c768a0c56bcf02b" - execution_block_height: 341 -- deposit_data: - pubkey: "0xa42f4e4ec8732b5a4fbffea9e17a6c208db024f36a899f90671b1910a1dff4b18996b517050f4aacb548640bdf8fe844" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa9bd07d645a9db4c36bf8bb92478ee618c92414a1619a04cf6d39f5ca4fe37fed016b0332d9e9ba62247180f26b001d40c7ffdd2522fa4316e5f1980b14259b279e98da54c01a65d1355d6ba3e827b36476854eef5fce97f01690bd45c9eb7bc" - deposit_data_root: "0x0cb2bb82034b6d90b373320449a801ae41fdc49cb4610e8bde86a2073cf91e3b" - eth1_data: - deposit_root: "0xc8efa837e5b208a1252d1c5caa6e2da3377532093f952df3d8a655cf0960144c" - deposit_count: "341" - block_hash: "0x281cce8e3af1cba285ad4a6533cbbdd4b16f7c646351d2594c47a54d84685a01" - block_height: 342 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x691d6c3d1788448165cd972951b94cca4485afc7571864393cb986aca78a99bb" - - "0x0cb2bb82034b6d90b373320449a801ae41fdc49cb4610e8bde86a2073cf91e3b" - deposit_root: "0xc8efa837e5b208a1252d1c5caa6e2da3377532093f952df3d8a655cf0960144c" - deposit_count: 341 - execution_block_hash: "0x281cce8e3af1cba285ad4a6533cbbdd4b16f7c646351d2594c47a54d84685a01" - execution_block_height: 342 -- deposit_data: - pubkey: "0xb4368a6946801366550e764247f27c24bed3c5830a49be21ddeee79a67d71e0aff3d5cc0a9b1e18bad5c9e655ad23502" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4e0e77af815e173a20d602fbc5148918012820754d736dd34549761392b3d8a81af062830ee3d851125ae056201dbab1246b20bbe4d92a3e11f913fff280acefb6dd64508591e1ab9b02a300bad6ebb54e205c80923d892a8db4564c3945856" - deposit_data_root: "0x3c15bff5b52d7d846681e0cd70606a0ee32b276c9408b42601fde515b35f196b" - eth1_data: - deposit_root: "0xa10f1b573d7d012d556954b1fb79a14483d8d45db59f475f2ed8bd0dd38a2f79" - deposit_count: "342" - block_hash: "0xfcef96a865323635b735de16d7a4e79e58e00efb50920ae9b7f55cbec4dc42c4" - block_height: 343 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x691d6c3d1788448165cd972951b94cca4485afc7571864393cb986aca78a99bb" - - "0xe8c655eec2a2badc6e31a521cd3847d22c7d67433e11aa8304ba846ef1b4616e" - deposit_root: "0xa10f1b573d7d012d556954b1fb79a14483d8d45db59f475f2ed8bd0dd38a2f79" - deposit_count: 342 - execution_block_hash: "0xfcef96a865323635b735de16d7a4e79e58e00efb50920ae9b7f55cbec4dc42c4" - execution_block_height: 343 -- deposit_data: - pubkey: "0xa4a216ff9a365d29e027a557ae3d4f52bf4fa11654c4314d65a319810fd799c8fb2d129692d7d68f6c134cde9de5d2c6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb62b67e03a9b18faa69f527953e53ca8dc3c14ff793e274314c0495118e52e01651bf43cbce50daf8c93756a43a32f6d10f697c1a37842f1def52372e984fab30010f35b5105f1079563979690d46edc71c80fa6bae135b49a2d7b0a01aad0bd" - deposit_data_root: "0x0f433740b767093be9a8d4e8ee049901f378187296b76b352595542022e6bc19" - eth1_data: - deposit_root: "0x0cccf3ad07bb6129c55118f363f08f6a7eab37150d087d713ab9e4b93c9e3825" - deposit_count: "343" - block_hash: "0xfa34f90576211ca0acfb7db7cbf1529ddb54a8f319986a7c133b79e15c65f945" - block_height: 344 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x691d6c3d1788448165cd972951b94cca4485afc7571864393cb986aca78a99bb" - - "0xe8c655eec2a2badc6e31a521cd3847d22c7d67433e11aa8304ba846ef1b4616e" - - "0x0f433740b767093be9a8d4e8ee049901f378187296b76b352595542022e6bc19" - deposit_root: "0x0cccf3ad07bb6129c55118f363f08f6a7eab37150d087d713ab9e4b93c9e3825" - deposit_count: 343 - execution_block_hash: "0xfa34f90576211ca0acfb7db7cbf1529ddb54a8f319986a7c133b79e15c65f945" - execution_block_height: 344 -- deposit_data: - pubkey: "0xa814035503cb1adcfdef259521573a5193dfa1e8d74e22649ad1b2b01a44fc6c2352100bc42d6c48584ff1d3a430537d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c4f917adb8706a169c9f93c6bbe2186fd84a81e86f9adaec27391a485afc8e9130f5d940129208437ac6be31ddb50b50eeefceb47751b664ff119be7aee930df8bff802a819c3e9d08e0cc453b7544bc8089b8b9f63c8dc7e2411d997aaecaa" - deposit_data_root: "0xea73c129f0be1b4bb10e06fbbe409b22e94e0e4ad06411ee5ef1142fd61b3c64" - eth1_data: - deposit_root: "0x5a82e52a1ed416eee15a1c097092f7518b4c53a89bf44c76046cbdb4866aee0d" - deposit_count: "344" - block_hash: "0x625d21649e1542510b4fa7f2570067aec4ecdbaade993f1b33f7fc0448ada465" - block_height: 345 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - deposit_root: "0x5a82e52a1ed416eee15a1c097092f7518b4c53a89bf44c76046cbdb4866aee0d" - deposit_count: 344 - execution_block_hash: "0x625d21649e1542510b4fa7f2570067aec4ecdbaade993f1b33f7fc0448ada465" - execution_block_height: 345 -- deposit_data: - pubkey: "0xaaa334a2fb779ce6de3c557025ff9bf17243deb3f09a37f6719356b54afd9be09a7b82cd589758779544a08cfd3ed5cc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x951f84c57cf1242ce760108e8b5e6ab1b9ea7bbf8ce40198f6a10f2d55838320c9f6e0c2bed2d9eaa4aa3837a9394fcd1562efaa6daeb5b8e0b183924d9786bd2eb94b31952577611f3e1eec834e44fcfab0cacec4632aba7d1ba5a15880f339" - deposit_data_root: "0xb6d2d582e7c0f6d7dab60416797b7c84771fe30cb380b25be8c6c0ca12d7c54c" - eth1_data: - deposit_root: "0x9c2315289da9d6fa488347b4856f84fe558e5e7e186b44256496a538a126f880" - deposit_count: "345" - block_hash: "0x51046db7f7057439306e943428b8271dceee739b9c2473f00c516959dbec5bfe" - block_height: 346 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - - "0xb6d2d582e7c0f6d7dab60416797b7c84771fe30cb380b25be8c6c0ca12d7c54c" - deposit_root: "0x9c2315289da9d6fa488347b4856f84fe558e5e7e186b44256496a538a126f880" - deposit_count: 345 - execution_block_hash: "0x51046db7f7057439306e943428b8271dceee739b9c2473f00c516959dbec5bfe" - execution_block_height: 346 -- deposit_data: - pubkey: "0xaac004b4b25584ba8c906671196db57d7a4f159cfb430b1590d61d94c5c22cb694c25bb6ee850421db260d063e096145" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xac4f0ad93d329006d74a5e51870edd9038900d3e080f74ad8e3362fff86e616566dde3416b752f7b900e235bcf01f58b06862f9678a0dd11d481224afdca5ee9fdaff661e74b4cb70cefb7f691b76a822fa278008f9b611ba9cbb5a0234ccff9" - deposit_data_root: "0x8b7c5eb357e9e4abb96dc68302fa6e93c3067abd623b9338a1afa7ac8e16d63b" - eth1_data: - deposit_root: "0x5ce81fb6b5ba660b0fd450e1c1d8ce22f1b02dbfa8d4f1df5062d70e3aeb66e4" - deposit_count: "346" - block_hash: "0x51050db6a05c59f2dfcdf79b39dc420a5d52ad575ce0f08b436f6ea02325d0fe" - block_height: 347 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - - "0x5a89c435553780c594308fa4372c85b8ac39ab611cbdd7ada19bb99116c7cb4f" - deposit_root: "0x5ce81fb6b5ba660b0fd450e1c1d8ce22f1b02dbfa8d4f1df5062d70e3aeb66e4" - deposit_count: 346 - execution_block_hash: "0x51050db6a05c59f2dfcdf79b39dc420a5d52ad575ce0f08b436f6ea02325d0fe" - execution_block_height: 347 -- deposit_data: - pubkey: "0xad7b6b1ba1f7950cc8e2b5b0cb9041666361eb023b1deff783420482970cf58213497d64cfc91cf06762dd511b0aac31" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5abc0b6d2aeeb6156bd77d50cb67115abe5b519e46a9275f60d02128e2048da62556ff24746037f710a4d5cae3d924c1916f896829525603a9c8a4f7c737c7e9255344686fdb590bf8dc9a6ed3e4803727ace8ac5346cdc650aaf87444cf316" - deposit_data_root: "0xcca6725f342e727e05d46aacf15407b225f054ee8edcde7c1e46554ab4d258d8" - eth1_data: - deposit_root: "0x46aad6ffcc251664c097e6d284162dc501d4d1625ac3585cc2bee8bee47ad946" - deposit_count: "347" - block_hash: "0x9660d6f0d6d69745bc825562e29c177a86f4ce447e9d90f417f66696454551b2" - block_height: 348 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - - "0x5a89c435553780c594308fa4372c85b8ac39ab611cbdd7ada19bb99116c7cb4f" - - "0xcca6725f342e727e05d46aacf15407b225f054ee8edcde7c1e46554ab4d258d8" - deposit_root: "0x46aad6ffcc251664c097e6d284162dc501d4d1625ac3585cc2bee8bee47ad946" - deposit_count: 347 - execution_block_hash: "0x9660d6f0d6d69745bc825562e29c177a86f4ce447e9d90f417f66696454551b2" - execution_block_height: 348 -- deposit_data: - pubkey: "0x846f3af6ac23b648a5ea3cdb60b2086e18e42b2837a9d689625af534792f9489a1b22cab64476533993f34149fed1a0b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xae4c2827395dc47a9303a2ec5f5337b5fadb67e813e4bdf0bee32ea1f0a0377e50f1f381313d4d8db576797ce1f1edbc1924e110278ce87761774b7fa6250f81f0563a2c757ce24b7e57143a396087a2a14dae9f06c2787cfb247e8726df18f3" - deposit_data_root: "0xb6be3a5becdd7330a4e3fe5176fc77cc1cc107629421a6c373ccf5cf89d12546" - eth1_data: - deposit_root: "0x473a9d413c0619796c3ffa906b9ba7774339cc08d3f30d468d868ac3ea1fef6e" - deposit_count: "348" - block_hash: "0x25df98dc1923be776e2e9732e5e667a77f6dee8af43bc1f198dcd43025da3589" - block_height: 349 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - - "0x08b50aac114125944b1e0be249589266d73343598f6094b9e13f1c04c4f895fe" - deposit_root: "0x473a9d413c0619796c3ffa906b9ba7774339cc08d3f30d468d868ac3ea1fef6e" - deposit_count: 348 - execution_block_hash: "0x25df98dc1923be776e2e9732e5e667a77f6dee8af43bc1f198dcd43025da3589" - execution_block_height: 349 -- deposit_data: - pubkey: "0xa8784f36c1a8c6e739b090d982480712ff04c9231190b97121c86c84a35cbd561d3372902017ea71fb99ce2a6582f362" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaff0df82cd7d8b804a9f5f62f493936e0d77be3631c1f0d9d295dac98c3d019f534774519582503694a723820031ddb8184de493e5df34c2fb875ee4a0be4c82571b6623546b666d1ce1c539175662b42dc963e6205a243d758f7e1e6c040449" - deposit_data_root: "0x8920aa96089e1b189ba5b62754537f9ae53cf2e3f6d0d9c18385bc2566712a24" - eth1_data: - deposit_root: "0x69b38abb0d6cb1a2914ed20b9c04e2110e2374dcae202360845a9f730e2005b4" - deposit_count: "349" - block_hash: "0x74905823b1a94f841f1763c2b21604f9ddd31727e12a0c4405a50b70903c105d" - block_height: 350 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - - "0x08b50aac114125944b1e0be249589266d73343598f6094b9e13f1c04c4f895fe" - - "0x8920aa96089e1b189ba5b62754537f9ae53cf2e3f6d0d9c18385bc2566712a24" - deposit_root: "0x69b38abb0d6cb1a2914ed20b9c04e2110e2374dcae202360845a9f730e2005b4" - deposit_count: 349 - execution_block_hash: "0x74905823b1a94f841f1763c2b21604f9ddd31727e12a0c4405a50b70903c105d" - execution_block_height: 350 -- deposit_data: - pubkey: "0xa398c76ca4aff66b20dfea97e1bbea311a5491b6de44cbcc979bd368818eaef834f188ad6fb413c3120b1af141637cdc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb345cf97868f004b66e78cce43b072e515e647c10b7a06970bf335d4eb67d423977bcbae4ddd3479fc243117a639443a0f730fa27c9173a1fe9c1a336d99aad931e813fc84f11112453bae446b01a363f5a8962bb2fda2957f5cadd0bdbcc840" - deposit_data_root: "0xe1e4024334ffdcc89d3c6bb0f633afb6b8cf63e1bf0624dd29e980035a80b1e1" - eth1_data: - deposit_root: "0x249ae18bac8cbeb1bfb2970234b73368a09d8a2ed6fab14c1a0608887b22c95f" - deposit_count: "350" - block_hash: "0xe55ef2c7c927098d257769da87f33354267c4f720bd31ee699cae6fe6c23b9ab" - block_height: 351 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - - "0x08b50aac114125944b1e0be249589266d73343598f6094b9e13f1c04c4f895fe" - - "0x540b74da4539ce3fc72ade01431e3b30606bc85b85cc5cba0c12c3a166746e9b" - deposit_root: "0x249ae18bac8cbeb1bfb2970234b73368a09d8a2ed6fab14c1a0608887b22c95f" - deposit_count: 350 - execution_block_hash: "0xe55ef2c7c927098d257769da87f33354267c4f720bd31ee699cae6fe6c23b9ab" - execution_block_height: 351 -- deposit_data: - pubkey: "0xaf54a32535604e042dc5a7e296a762f3ddc1fe00741eda9acb8f54be49a8244750d6d8f15a10d9384afbd26bccb662af" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa6286e34d6eabfd35e2e69f1541ce7574229d152a95b693b1073874d7505399babd68b1e5d373c5e3a60bb85360d43a51715914324b85b6d52ecb42f129bb1658959ee201c581e717a1ec40e281fa2ba26e2d93f1def180d2d13a58ff9b875e4" - deposit_data_root: "0xb1c68778c0ec68fe070016e011f0efe683dcf0d5ccc5ce4a41506027de9409ab" - eth1_data: - deposit_root: "0xf653f23a48facb6c1c9b5d7df3387f77ed798e75556b335237fcfe8e40e6832b" - deposit_count: "351" - block_hash: "0x22fbc60289a0213fd65514e255963d148ffac49400e25fc2fec770a6abc36890" - block_height: 352 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2be3d691d07573381ecaa9be38f015a03ca00046e7433389a53d83968989b137" - - "0x61af53fab3c4897138fbefac5b631a96d505678a097ecfe9a2f9a7905c8d9683" - - "0x08b50aac114125944b1e0be249589266d73343598f6094b9e13f1c04c4f895fe" - - "0x540b74da4539ce3fc72ade01431e3b30606bc85b85cc5cba0c12c3a166746e9b" - - "0xb1c68778c0ec68fe070016e011f0efe683dcf0d5ccc5ce4a41506027de9409ab" - deposit_root: "0xf653f23a48facb6c1c9b5d7df3387f77ed798e75556b335237fcfe8e40e6832b" - deposit_count: 351 - execution_block_hash: "0x22fbc60289a0213fd65514e255963d148ffac49400e25fc2fec770a6abc36890" - execution_block_height: 352 -- deposit_data: - pubkey: "0xaa41628c60365750c75cf91a8d4097047d61c624b01d4f298ac90a66409cada1bb99cf42a2577ba173a94745dd73d6d2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96b47d0a7ea06103c1ccf89698c71e8e05bb361aa564c6a0ddde8cb35ca0827d4c6fc03ef762f9f8919907deb4b7e2c10210757f9a0e03c7edc7e93afe48c3049841940bb352d015fcc404dd065d9aa947aba87074344fa29e126d83165f9e10" - deposit_data_root: "0x5809f4140599df5900ef54b70e678188108fcb84993cdf92f63734e016683a36" - eth1_data: - deposit_root: "0x8fbfbcd57a3f6f5dc37eb7ec56d7e273892dc5e321c1365d220becc4ae65eda2" - deposit_count: "352" - block_hash: "0x5fa4ed804971ad0bc19fd3038282bb697dca9c7aaca9b413f54fb261e843352e" - block_height: 353 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - deposit_root: "0x8fbfbcd57a3f6f5dc37eb7ec56d7e273892dc5e321c1365d220becc4ae65eda2" - deposit_count: 352 - execution_block_hash: "0x5fa4ed804971ad0bc19fd3038282bb697dca9c7aaca9b413f54fb261e843352e" - execution_block_height: 353 -- deposit_data: - pubkey: "0xa4623cedd189249799b7c9afb49960dd86f31572a6edd594dc9d147bd11b368b66afaff6a2dcdecfa873f88991e5a8d6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa53f69cf2e224bfff70ba77e95b49525e4222ff8f3967a79bcc6244995e48501db0556fc79fba5fb32bcf1cf45c1ba8f17c0eb25eb5fbffc63344807e2627b49e03f8d8e8e9017c0a65f35702f2353eb576141fb10970655d63ac5d89a2e87b8" - deposit_data_root: "0x79ac2faba3df339c9a4c1035da2b4319b9b6e007a70479b528b6eb6008281e2a" - eth1_data: - deposit_root: "0x3d14a58b5cbc0e3146ad1778955f955c855ae299c4dbb24494e291aac4bca411" - deposit_count: "353" - block_hash: "0xd45a6c4a1391e32940ab1a47bde642ebbeb12849a6909615c317474252c04075" - block_height: 354 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x79ac2faba3df339c9a4c1035da2b4319b9b6e007a70479b528b6eb6008281e2a" - deposit_root: "0x3d14a58b5cbc0e3146ad1778955f955c855ae299c4dbb24494e291aac4bca411" - deposit_count: 353 - execution_block_hash: "0xd45a6c4a1391e32940ab1a47bde642ebbeb12849a6909615c317474252c04075" - execution_block_height: 354 -- deposit_data: - pubkey: "0xa58a5d3d29c1331b6f4f977ee08afdf95c067db26c21a591b15db682f93d223acd4e031c98434ec7be34922496ec8ec1" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b2019cbdc2fb49df3257128d61a4e0fa63f451b09a86ef0e5b3084d42a7647d076f03771c996322ce65a4ee05a9bbbc0e7a108e2b816bdb39db1ebe2cc10856864cd8dfa2a042d42fed42236d88b37ef286bff4c5e3176140f140773ccd5505" - deposit_data_root: "0x4e2e9ae16bcb8f83129a723e664b4e2d1dbb436655d0c2bfcacf416ca71b3bd0" - eth1_data: - deposit_root: "0xfc17404478ee17d526ec0e6d7b7f392f0cd2edf08cd20ea72a6b7cafb0bc5d1b" - deposit_count: "354" - block_hash: "0x48e867e23f93afbce5c768c3e7b24afc6f67bbf23b03ed35f673daaa22e150cb" - block_height: 355 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x0e5edf133dd9d31c962d3c832701f118549e950098920f374a8a7f4b1503424c" - deposit_root: "0xfc17404478ee17d526ec0e6d7b7f392f0cd2edf08cd20ea72a6b7cafb0bc5d1b" - deposit_count: 354 - execution_block_hash: "0x48e867e23f93afbce5c768c3e7b24afc6f67bbf23b03ed35f673daaa22e150cb" - execution_block_height: 355 -- deposit_data: - pubkey: "0xa31ecb88e06673a2c3ea98a969827babdd0c6a8fc9569d70c024640f31084c7fc2bc8f00eb65023a008e0b47b3d2e364" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9212c805e5710672e72e9cbf49ab23f0b8849bda05c2eb97599849b0966e814956cceff71141ee1c90a5090e7388e6ce167364b1a5a843e08ab11e42fcb452e70a9b34cde657637ba57002054819440d51b7fefc316897c2dc396e36e3052251" - deposit_data_root: "0xe29cc36e9033d65ab7c3214886e01b4071597f461fba82ca0d5b6e2716a96c2e" - eth1_data: - deposit_root: "0xbc150ef54a71de32ae1cdc80595d7102bcfb75391a31596cd08f93ac573f82db" - deposit_count: "355" - block_hash: "0x91e6f47ff5874c78e63536737ed9fba1fd13418ad4adafe5d8afa6b6096d7175" - block_height: 356 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x0e5edf133dd9d31c962d3c832701f118549e950098920f374a8a7f4b1503424c" - - "0xe29cc36e9033d65ab7c3214886e01b4071597f461fba82ca0d5b6e2716a96c2e" - deposit_root: "0xbc150ef54a71de32ae1cdc80595d7102bcfb75391a31596cd08f93ac573f82db" - deposit_count: 355 - execution_block_hash: "0x91e6f47ff5874c78e63536737ed9fba1fd13418ad4adafe5d8afa6b6096d7175" - execution_block_height: 356 -- deposit_data: - pubkey: "0xae32f510734cfe1a05f1a49888185191777008667a62a3b5d71f4b249a5a0421d762c98c0dd0626e17f6326ff525e63b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb278d4156302165853f5a95e5a38a57aad92e6388d231f1616c024ece355b83f9b4ba41f1bb60fc0ae5aca0c1457b5090e84bd06d28288890b17a2244288e4421a4fc1534b190ed3e520a471701e5142eb4374f3e372f4e7b0d754e943455b54" - deposit_data_root: "0xea9cace5a73c0c0a4cfc34fe161fe4a0aa802729c46451cd375a524f84032e1b" - eth1_data: - deposit_root: "0x4ece22540ed44b8d7e55a580ef7afcd709ada5004c4505db558a4fa0ef39e949" - deposit_count: "356" - block_hash: "0xf9854d07f70ef80e24c9dae43433d2a284b89ee66e5e8912a4ebb92246a268da" - block_height: 357 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xec97a0de415ce5348ebe21219a3cbe17938f07344276f64ec40e1dced411c131" - deposit_root: "0x4ece22540ed44b8d7e55a580ef7afcd709ada5004c4505db558a4fa0ef39e949" - deposit_count: 356 - execution_block_hash: "0xf9854d07f70ef80e24c9dae43433d2a284b89ee66e5e8912a4ebb92246a268da" - execution_block_height: 357 -- deposit_data: - pubkey: "0x9407a54a336fb7632ed16da2929c6ed1e2a8a969990d15f9f322734ee60c00757dec74e4c0224e813efeb1c9be42c09e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xafd6a5b56011fdc9d506d7c468993d690e2416b8322a5117fbfaeba9e4f15a6518d1f6b4b6917f381e96e396f8a5ff0c13eb33425c2247af685cc4723322d0e1596a3e1d0013871ac6603ef6fa4c74eddf2707f60e19fbb84073113afaa9ce5a" - deposit_data_root: "0xa27fecc07480ce7de9e8fdcab0a5371632f26446c6a1a7e5594cd3b05f91c879" - eth1_data: - deposit_root: "0xb6e2c6e1112b2143ea4480f062c4c7c648307419c43032304281f44bcfe04578" - deposit_count: "357" - block_hash: "0x624663feb3f1449b2b425c886c243ab42cb4bbe01a06f1d47421134df7b63475" - block_height: 358 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xec97a0de415ce5348ebe21219a3cbe17938f07344276f64ec40e1dced411c131" - - "0xa27fecc07480ce7de9e8fdcab0a5371632f26446c6a1a7e5594cd3b05f91c879" - deposit_root: "0xb6e2c6e1112b2143ea4480f062c4c7c648307419c43032304281f44bcfe04578" - deposit_count: 357 - execution_block_hash: "0x624663feb3f1449b2b425c886c243ab42cb4bbe01a06f1d47421134df7b63475" - execution_block_height: 358 -- deposit_data: - pubkey: "0x8fe3971515fb25cbe62a03e4a593dfb4cc3563bc5b7866dfe300b958a53f07fdde463fc7abfe349a2eedf38d569cd126" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8f51e38f6033d91644cd646777b584c8ee985d18951ea4f184e5f321806b111881584c65d411542e99899832620664c60c6bee1be977e63041276f81f6daaf7b1931cdd4c725faf85b5186f2001b18e4e6cfb2c562cf5360bf76f1783ad54b7b" - deposit_data_root: "0x28cf518415e14159c030d4c9ce4e1737eb8dafd10f8c9f3668c1a157b5f5ec3e" - eth1_data: - deposit_root: "0x0b066def513f0615242e17a3fe521dcd78226abfbfa040203591f1f9587b3877" - deposit_count: "358" - block_hash: "0x3492958f15fa544dbe4eac07ce27c1ade01e53ce22cff0aa6e5688a50a53af73" - block_height: 359 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xec97a0de415ce5348ebe21219a3cbe17938f07344276f64ec40e1dced411c131" - - "0xd34bdde7bf541ae5af14a22f182fe4a5087d292e96c952ec405b9c04fd71645c" - deposit_root: "0x0b066def513f0615242e17a3fe521dcd78226abfbfa040203591f1f9587b3877" - deposit_count: 358 - execution_block_hash: "0x3492958f15fa544dbe4eac07ce27c1ade01e53ce22cff0aa6e5688a50a53af73" - execution_block_height: 359 -- deposit_data: - pubkey: "0xae99451101dde8ce7d7f1b784fe2a1fe0974cd3d7e8d2d3c775892cdc2dfc021eb9f7a5b60681fe7369ec88884e6299a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa9e4c4aa80a317292a1d42a7dd6d18cfb04c7087a078332b7ca9c1fdac29b8019a2f6f2cb3034362a63ac7ceed7616510b6c45212fd0ebc2b7851293c4c46d1aa3eb99b7df62891c30e83d2a1a58f5bc91ad4278655f8482dd14bb0941ab37c9" - deposit_data_root: "0x69b84b7fa3b0dda0b7eaf825ac386bd2416359a1c076743901c727adfbad4022" - eth1_data: - deposit_root: "0x0a872a65e20b1d6eb13282886f5082a99b3ac36e5636c6b7667d6f7b454c136f" - deposit_count: "359" - block_hash: "0x05f878ea1b32f42d1b5eb823c090f2789b19a062bc3495d80402a3077ccab36d" - block_height: 360 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xec97a0de415ce5348ebe21219a3cbe17938f07344276f64ec40e1dced411c131" - - "0xd34bdde7bf541ae5af14a22f182fe4a5087d292e96c952ec405b9c04fd71645c" - - "0x69b84b7fa3b0dda0b7eaf825ac386bd2416359a1c076743901c727adfbad4022" - deposit_root: "0x0a872a65e20b1d6eb13282886f5082a99b3ac36e5636c6b7667d6f7b454c136f" - deposit_count: 359 - execution_block_hash: "0x05f878ea1b32f42d1b5eb823c090f2789b19a062bc3495d80402a3077ccab36d" - execution_block_height: 360 -- deposit_data: - pubkey: "0xb5dd36fb996d68fb9dff117081435088b54509db6c3bdcbe58bb3711fd21b62f22f7ffef9b8ead2031c0c187717ca738" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa704de41a3d802906eaa920016df241c8e6a483b25b6a144811305341d631c9159b7453c16fc7266c005d20a362b52ea1322e545ccb84f995f863b30f5b31d0af2824cbd21cf7dfce43fb369cb77cd92dfa58ea2c515d99c4b1e82b30a4001ba" - deposit_data_root: "0xe392df1901cb47c60f7a23312f4938957258d1e4552775a6c9e1716318ebc23c" - eth1_data: - deposit_root: "0x566ee27dc76d160c182b89a8152d85644ad00ae1a6dffa89e096d383f612e4f0" - deposit_count: "360" - block_hash: "0xe3a5318a234c6d5ceddb72a5dbcdd3d1424a17d8ca7b08bc7bbdd55b6aa0d9c3" - block_height: 361 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - deposit_root: "0x566ee27dc76d160c182b89a8152d85644ad00ae1a6dffa89e096d383f612e4f0" - deposit_count: 360 - execution_block_hash: "0xe3a5318a234c6d5ceddb72a5dbcdd3d1424a17d8ca7b08bc7bbdd55b6aa0d9c3" - execution_block_height: 361 -- deposit_data: - pubkey: "0x9376161dba759909776ec807351e1cdafbff9a9d817babba680c953344a80dcaa38378ac6a7fb3079d909f5907310336" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b38fdafe54fb8e27ad780c4fa0910777e6d0f8ece579bfbd4a0cf121eca3de1e1e19dfdf284db7516d25265f9c989210ed3894e1c6c6e3365d99e9f3357b39bcf791ea2e08c43059e2adc753ea6c96a68ca0134395f7b8cb25662d87339157b" - deposit_data_root: "0xb1df04177e3328845d9af042b46eb4b7b5d4d0c8c884a28c2ad183ed23801013" - eth1_data: - deposit_root: "0x77959d8e2c2c5ce3fda7eb96f376f88a281224e3eaf3fe232d20d35eef2d4f2e" - deposit_count: "361" - block_hash: "0x42046b4ccdbc910edab7da07038bb06f7cf7a9f01da446b7896260e2fb0a797a" - block_height: 362 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - - "0xb1df04177e3328845d9af042b46eb4b7b5d4d0c8c884a28c2ad183ed23801013" - deposit_root: "0x77959d8e2c2c5ce3fda7eb96f376f88a281224e3eaf3fe232d20d35eef2d4f2e" - deposit_count: 361 - execution_block_hash: "0x42046b4ccdbc910edab7da07038bb06f7cf7a9f01da446b7896260e2fb0a797a" - execution_block_height: 362 -- deposit_data: - pubkey: "0x8d92d91ccf259f1c3df11d2e4e92cbe047a9b278ab9ee1341835a08da4c957950a70f51d62bbefd4c6f3c01511e39796" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83b14219e42c4389f315892b1f7bf34a7d9649aa91086b2a3a5d06fa5822af504b9f46364e39a5406a33bb8feb3baa540f90dda0d091ca6cd9df322bb81e1220af8a63a3ac9f5bea4db7ea883d8e7b39df07c59f7d478c9754455e84f556d1dc" - deposit_data_root: "0xb65dfed61fae9bb5cfe7b84bb94b11aed1949d8c0f2ab1bad962aa2639ee9ea5" - eth1_data: - deposit_root: "0x8ce66b4067fd288192b5cf456c79dbf793d86adf144f0ded5837fa2f916330dd" - deposit_count: "362" - block_hash: "0x30caf55961a0afa34e0ea7fe1c82badaf3bcb966c3e27b5f6d28f5dc90cdac8f" - block_height: 363 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - - "0xededa6672e4f4f520b91640b5d0e396d02af2d267ed8a7f99ec6844a81bd3197" - deposit_root: "0x8ce66b4067fd288192b5cf456c79dbf793d86adf144f0ded5837fa2f916330dd" - deposit_count: 362 - execution_block_hash: "0x30caf55961a0afa34e0ea7fe1c82badaf3bcb966c3e27b5f6d28f5dc90cdac8f" - execution_block_height: 363 -- deposit_data: - pubkey: "0xa95aa6bfc756de15cb1a21d05b465afd5bf806faee4e662ec335d1e093299632f9498dff50c71844e28d98b0d2cb9e29" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xab509e72eb0293f13ff2cb2dea99e6289562f8315b1f70c913f1a7705b45c6398150df3cc1991edd1ef7d0c72cd0925712a04bf9161f3f7d5548ec6f0ec4c2271054fcca875d6258a204d5478a4396dafcb2d99083ef2c990111cbe6c9b62b07" - deposit_data_root: "0xf0a5d8ef855f6d121a251189ad5dfe512c221f0f27e4f7225fc37e163001359c" - eth1_data: - deposit_root: "0x8c048d62dd48e2676a75bf37278cf8a81934a702809656d92f153568b81893a6" - deposit_count: "363" - block_hash: "0x7dbc675b430d7c2de00d444ad4babab187b9590770a385b9c34b97f8f9db8bf7" - block_height: 364 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - - "0xededa6672e4f4f520b91640b5d0e396d02af2d267ed8a7f99ec6844a81bd3197" - - "0xf0a5d8ef855f6d121a251189ad5dfe512c221f0f27e4f7225fc37e163001359c" - deposit_root: "0x8c048d62dd48e2676a75bf37278cf8a81934a702809656d92f153568b81893a6" - deposit_count: 363 - execution_block_hash: "0x7dbc675b430d7c2de00d444ad4babab187b9590770a385b9c34b97f8f9db8bf7" - execution_block_height: 364 -- deposit_data: - pubkey: "0x89f4dc26316e4037269b4afd3d92f7aa9d768be37dd951829657941f2220f7546071ac46a72d715a1af71689982afe61" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb7be13f710902b118aabf1b4e84f6e67bb72bb4df89a9444dffcdc9db966fd451b4f16cebfa6546a41f0b7ec4a52baa206133d4190cbcf26b266d5f0dda6231d720a0cfb21c671008b84b230f851a4e9bdb87c78f81a2c4cee31bea858596c68" - deposit_data_root: "0xbbd775efe2df031eb9719e580ea4259844d508dcaeb723b6698c3896dee27771" - eth1_data: - deposit_root: "0xf5ed206c675198fb290d0298805bdd34ecb569f3a14bcddba8343424118d0609" - deposit_count: "364" - block_hash: "0x8969f759d3bd762bf45f15e4a30f4c733ac0a822a698027610cac5618cb35e91" - block_height: 365 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - - "0x6ad91868ba8f36bf1cf9fc3a3226e8fa1b5b3182911ab1db6c7bfd830938d689" - deposit_root: "0xf5ed206c675198fb290d0298805bdd34ecb569f3a14bcddba8343424118d0609" - deposit_count: 364 - execution_block_hash: "0x8969f759d3bd762bf45f15e4a30f4c733ac0a822a698027610cac5618cb35e91" - execution_block_height: 365 -- deposit_data: - pubkey: "0x812c4ef835a77db7ead5b86773755711051cbc34cfdc4be7eb41a99ce5094f7d12412a7215275cd9ae8d8426eb91dfb4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa6785218f754ef90bb5d5b308a6b1bd2078c6d1c03845c874515ae4fb2da4b44dd4d34dcfaf5a5057b8f90d0e9154e56155038a0dd687eca319de2270b634a6d0038f72a05b6c89e85c00c53a4ef259dbe3f1300c7bf4c63c2fd1e7b62488abd" - deposit_data_root: "0xdae505a2425fa70ac7714c907d1918d77c0ef04f4654bc884353791f0b148071" - eth1_data: - deposit_root: "0xbab7a3b70441e52c82a2a43408ca1f6957abf9f2189daca60838341f23a14cbc" - deposit_count: "365" - block_hash: "0xc56ca86bea734411f9a9a8ce247094bbd5d632888cdbe1938df31acb880e662d" - block_height: 366 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - - "0x6ad91868ba8f36bf1cf9fc3a3226e8fa1b5b3182911ab1db6c7bfd830938d689" - - "0xdae505a2425fa70ac7714c907d1918d77c0ef04f4654bc884353791f0b148071" - deposit_root: "0xbab7a3b70441e52c82a2a43408ca1f6957abf9f2189daca60838341f23a14cbc" - deposit_count: 365 - execution_block_hash: "0xc56ca86bea734411f9a9a8ce247094bbd5d632888cdbe1938df31acb880e662d" - execution_block_height: 366 -- deposit_data: - pubkey: "0x8cfd6c4fe7aee61657f1d6e5cff1d41d24bafab7ceea78d2f5d680f8b51aeea83ddbb39bf6978bbf1327a2d240012551" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8e2e30e4d30c5401f9fa6260da87b0468c53580f230c36255690955f4fddf3433da05d174390d687b79749afac5870c19b33d828ee655d6084b4a346df8d0aef3ef8491e99855e80d9e6413d0e28ae091303e3af2148166eace0d828eecf868" - deposit_data_root: "0x2873cca6fb60ed389212d5624bc18fdd882ccfb57d60c6fa5858bcee68ce8f8d" - eth1_data: - deposit_root: "0x31afd21b630f676a5b7673355f85afc5c2b4206ff10a7200586b0a61e918c632" - deposit_count: "366" - block_hash: "0x6d39ef06bbcadfcc1889b64112e7060c1f2065d6b254466e765603decd8b505d" - block_height: 367 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - - "0x6ad91868ba8f36bf1cf9fc3a3226e8fa1b5b3182911ab1db6c7bfd830938d689" - - "0xca7eee30dc4dae479488e27d2be31d92c9f9e2e3cd1b76c56bd8c0f895399c1c" - deposit_root: "0x31afd21b630f676a5b7673355f85afc5c2b4206ff10a7200586b0a61e918c632" - deposit_count: 366 - execution_block_hash: "0x6d39ef06bbcadfcc1889b64112e7060c1f2065d6b254466e765603decd8b505d" - execution_block_height: 367 -- deposit_data: - pubkey: "0xae7c6ebb4d7b5f462da2004698a6dc54f1e55ef0f3ecd0bc155c9b58f67a038422e209492c86c986d8f24f5261126f51" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5e4a27b8a2a022d4f4748b6277a7a3400866ccbef70d8b13578af0227c4491a9fb4934e23342bef47277ebbecebbcf101334a722540f70c1d3d768c870cfbf2cc3fba8cb7260dcb794b455083f4a092e54d441d5acee80b57d9d9ca8ee34057" - deposit_data_root: "0x0b99abe0654b3fdb3e10c60bf63da44170f75887fb61a4be8119ac5d7f84f7e0" - eth1_data: - deposit_root: "0x25e729835c195d00e120e374d28e26a9e19eeab013878aebbc5b37b29f2c61a0" - deposit_count: "367" - block_hash: "0xb187bb0435d4f02e62beaaf1833b7ae3cd93e1acc0fc9dbfdf24f6d0b48d3d1b" - block_height: 368 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0xc93d9dcfae7c9e728958d0b093f1fe4ae72fb7d51eb5bc91a9a106e417426fc1" - - "0x6ad91868ba8f36bf1cf9fc3a3226e8fa1b5b3182911ab1db6c7bfd830938d689" - - "0xca7eee30dc4dae479488e27d2be31d92c9f9e2e3cd1b76c56bd8c0f895399c1c" - - "0x0b99abe0654b3fdb3e10c60bf63da44170f75887fb61a4be8119ac5d7f84f7e0" - deposit_root: "0x25e729835c195d00e120e374d28e26a9e19eeab013878aebbc5b37b29f2c61a0" - deposit_count: 367 - execution_block_hash: "0xb187bb0435d4f02e62beaaf1833b7ae3cd93e1acc0fc9dbfdf24f6d0b48d3d1b" - execution_block_height: 368 -- deposit_data: - pubkey: "0xa4ee2d6f77a08089c71194ab48c338632c6b1041c326a483033aecd1bac1baf5de221053ea15c9fbdb8584edec6a731f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa868efa2a6b92d1cc547bf3352b67b142dcb106cffd3510caa09eca8071bac276d217af7aec4551b8da80f1dd7c5d62117c74d40e9204580cf0d68ef795e9c226ca17a0cc54f8da1fd5c9ed4a9721c27f646d3c8166cf711de031d1442793bd7" - deposit_data_root: "0x8ecabb629b1e1da971d5edf0f68c6f7e56a15adf30f5a224ced581e1307efa17" - eth1_data: - deposit_root: "0x71a7e69529e1627bebfe2eb84e5387960b67bfb45ea7c73b36e9f26b039abb48" - deposit_count: "368" - block_hash: "0x24625a939c0317893b0a4ff7e485998efedcc1219caf706cdcf98a955ac6eed0" - block_height: 369 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - deposit_root: "0x71a7e69529e1627bebfe2eb84e5387960b67bfb45ea7c73b36e9f26b039abb48" - deposit_count: 368 - execution_block_hash: "0x24625a939c0317893b0a4ff7e485998efedcc1219caf706cdcf98a955ac6eed0" - execution_block_height: 369 -- deposit_data: - pubkey: "0x82d62ceb1242d6dc6527d8f885fe301f4313b87e7e336a34a8d3a898ffca180dbc624f45e1dc07695cecc54023207400" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xafe8ea26e0356b1ffe34ee1f6e50d8f046ab3d7eaa3decae78fede0b56b8b04903031de2fb45a877b80ad5baca8dc6d900adf83f4617563bf9658627a40b601f5ee3e53980bc51584ee4c05ce88c3ca720b1214697668104dae210e7cbffaaaa" - deposit_data_root: "0x73aa59f819c871ac6651030e64fbeb030153e12d230d516c22b768230dd7c65c" - eth1_data: - deposit_root: "0x427c76f57c454b3d1154f3df3f645e88551b6d51f544aef9789dd63f26dfb705" - deposit_count: "369" - block_hash: "0x791a39d5642a805a00de9be73ed412bb5870ca828e2b8bd41e1ef0919336860a" - block_height: 370 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x73aa59f819c871ac6651030e64fbeb030153e12d230d516c22b768230dd7c65c" - deposit_root: "0x427c76f57c454b3d1154f3df3f645e88551b6d51f544aef9789dd63f26dfb705" - deposit_count: 369 - execution_block_hash: "0x791a39d5642a805a00de9be73ed412bb5870ca828e2b8bd41e1ef0919336860a" - execution_block_height: 370 -- deposit_data: - pubkey: "0x9337d74573784dae1f5badff018e4f1c5a2f19d1b57ac07a2f12dd628ed97a4b3a4c84ff4c7d291063f78ec15ec163ca" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96eb75a5657091aa6019a5b6755aca55b675e7ea964b4e88c4f3528d47768cf37b5edc524325b3f969ccfab550d0e3f4027ea30bb3a2ba00c9cb850a7391a7d37175b5f790ef21b710b25bfa0e48b18ea67d8f51c77fda44da5356750a1d77da" - deposit_data_root: "0x0bf34b47edadc088a1567b156f78bf0b3ba212689efb7262d3e992f106b79228" - eth1_data: - deposit_root: "0xa7bda8ebefccf3bf5ec32d0a67a48d1266fdcc537bea36a2c2e721aa2149be77" - deposit_count: "370" - block_hash: "0x47a677735c1aa41443778a8f1d010fd55ec000d87ea1b41d9f7b06e3ae6108f3" - block_height: 371 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x415cb916cdf12ea2e3a80a5a17730fb71ba09e3dee19953b2144ff451717f33a" - deposit_root: "0xa7bda8ebefccf3bf5ec32d0a67a48d1266fdcc537bea36a2c2e721aa2149be77" - deposit_count: 370 - execution_block_hash: "0x47a677735c1aa41443778a8f1d010fd55ec000d87ea1b41d9f7b06e3ae6108f3" - execution_block_height: 371 -- deposit_data: - pubkey: "0x91fa5a8682921fa6dce932b8a9cd3e58ac485c03ef81202f69635f463ced2b366a85a3b6aff905542840c05b5739d7d4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8360efa2ef67875dd7e4ec4d36b39f47adcee01a71d891c61a398413a3fd4e4d26f47f4dd72e4eb193f309d65409567302f99039c476d4f43a03d47285af9d9cd4ae8ab5ee1b9f6f0fc2c90511704414619a4de063ae1248fafc7d0c3822ec6b" - deposit_data_root: "0xde1d11e466b6199447c8461b2b04c55cc78b3d53aba3dd0f663c2eaa0e6f3d8d" - eth1_data: - deposit_root: "0xa6897fb6ab9ffb723e0af72cf18a2f1baa6356017f5931ac3a96050d0a02a0ba" - deposit_count: "371" - block_hash: "0xb6ea6ec5773f90cd394c362390e482e0526049017d7027be09902e00dd800d05" - block_height: 372 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x415cb916cdf12ea2e3a80a5a17730fb71ba09e3dee19953b2144ff451717f33a" - - "0xde1d11e466b6199447c8461b2b04c55cc78b3d53aba3dd0f663c2eaa0e6f3d8d" - deposit_root: "0xa6897fb6ab9ffb723e0af72cf18a2f1baa6356017f5931ac3a96050d0a02a0ba" - deposit_count: 371 - execution_block_hash: "0xb6ea6ec5773f90cd394c362390e482e0526049017d7027be09902e00dd800d05" - execution_block_height: 372 -- deposit_data: - pubkey: "0x8631b733c42dcdc91e0852655e1513864b50fd75ca1078a205205afd3d2c0de2cb2da63c06f4f547212132fd66bd7d16" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4258cf976f155bb4eb8566777cf34184a34b1c8090f931c9160c0b616afae1182256741674af7df73e11ff1db5dad7e165d952269a1aa8a68a523e87a9de14d00c3a928e97a1e7e74aab45331d12f23ec55fface9826bec0c71d3d985624b49" - deposit_data_root: "0xf449648967e51c3ddd171deffae8afbbd69753f29f5f65659a6a2c6bfe4191db" - eth1_data: - deposit_root: "0x96fb5489ed0128e41f8d22f65a9e02b05e3febfa35b4a0c2ead831cb68e0f1dd" - deposit_count: "372" - block_hash: "0xe7dba8e784a1897ee5404a1cfb172fd5e5897c784e72596431b8b6cd7caf0fa6" - block_height: 373 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0xfdb57c8fa36c5390b466ad79c9bcd9f3991ab173af3c339170f8c82eb63f0251" - deposit_root: "0x96fb5489ed0128e41f8d22f65a9e02b05e3febfa35b4a0c2ead831cb68e0f1dd" - deposit_count: 372 - execution_block_hash: "0xe7dba8e784a1897ee5404a1cfb172fd5e5897c784e72596431b8b6cd7caf0fa6" - execution_block_height: 373 -- deposit_data: - pubkey: "0x965258ea99b6f1eefc8d8196fad346c44b559e00fb31baf7cdda18e25b185eaafa91e3e366712514ab3ab855f41c9600" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb70f284e086d2d8d95b2fc7adf1937e52732e39294ef3b836df58c7cd8cb3fbada21137cd66070b82799cce72a4d741f0b66bfc892c5c3ec6ae1aa6e583f2abc05994af795607753085b556955cbd33df389e9ed86addc952a78a8f959c42711" - deposit_data_root: "0xb2b1703e6974ab25bd10e04e11c2e1f427cae39f2a5cade05266982616651ec6" - eth1_data: - deposit_root: "0x5af7c0eefdca4998093fee555e6b38acaf0ab646a17afb02712b2fc83f405d20" - deposit_count: "373" - block_hash: "0x5967feb20145233cf4deb33b3595e0f074a8c3af859acde36bf21d8d412020f6" - block_height: 374 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0xfdb57c8fa36c5390b466ad79c9bcd9f3991ab173af3c339170f8c82eb63f0251" - - "0xb2b1703e6974ab25bd10e04e11c2e1f427cae39f2a5cade05266982616651ec6" - deposit_root: "0x5af7c0eefdca4998093fee555e6b38acaf0ab646a17afb02712b2fc83f405d20" - deposit_count: 373 - execution_block_hash: "0x5967feb20145233cf4deb33b3595e0f074a8c3af859acde36bf21d8d412020f6" - execution_block_height: 374 -- deposit_data: - pubkey: "0xaff02f3635554e57d140069596a9c3463e85408521b2349b6d1d5c4223874086dfa5a58f8ca73256ebfccc63fb88e58b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa04bb8b421c6e3a97b2d678db62adffaded29db686da4adfe0e05dbb50e55cb95631a5298136778ac5d0fed413b291cf042ea8fd3406cb4cb196bdf190eac79e2e3526943df94b58465e40925255d38fcf6238069bfab4a3349d7e02490ce24d" - deposit_data_root: "0xb2bf57e882657383825bc6f7228e5a6c1c518c48de44a855ac853b521d8d28a2" - eth1_data: - deposit_root: "0xc88fceb356cfdc6fd64eb70a98fb873d921cbe2bfa77f357695682a1db122bfb" - deposit_count: "374" - block_hash: "0x4cbd9a54874e2dff03520d5313f7d0f436fd15e2e6adf2010430159f3c884345" - block_height: 375 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0xfdb57c8fa36c5390b466ad79c9bcd9f3991ab173af3c339170f8c82eb63f0251" - - "0xaa4ae80e4b97e7e7234fbf9b4581551d8b01473b0e06b9ae16eda5add93de3b1" - deposit_root: "0xc88fceb356cfdc6fd64eb70a98fb873d921cbe2bfa77f357695682a1db122bfb" - deposit_count: 374 - execution_block_hash: "0x4cbd9a54874e2dff03520d5313f7d0f436fd15e2e6adf2010430159f3c884345" - execution_block_height: 375 -- deposit_data: - pubkey: "0xa166c85614c5b0d5af165a42ab98515c009b6eccb740213e6ec1268e35aebaa73b8c9b22711aac0d5087f2534604eb36" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2f0a3f9d23101580d45e327cb58559a0eb0da2ef98ba8296040c0189c32cf5d400e49e45d408961a44ec1755f303be516949f3ed899d1b0afaa4cf3c237f7c1dcf538539d48c8b013601db131b2753b456bfa683922a1be60f306c491958146" - deposit_data_root: "0x635116788f81411826a4bab6c7b0c4d22d8a100053efca983cafed46f19c1128" - eth1_data: - deposit_root: "0x41e8cec2e3a73c8bf38474b5ca18043b11f11c9ee9eb0cacc2ffb740e5a57c12" - deposit_count: "375" - block_hash: "0xafa631155e92b5e36aa357fdcb2c59894f96d85f0cd7e7704da84c28de104088" - block_height: 376 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0xfdb57c8fa36c5390b466ad79c9bcd9f3991ab173af3c339170f8c82eb63f0251" - - "0xaa4ae80e4b97e7e7234fbf9b4581551d8b01473b0e06b9ae16eda5add93de3b1" - - "0x635116788f81411826a4bab6c7b0c4d22d8a100053efca983cafed46f19c1128" - deposit_root: "0x41e8cec2e3a73c8bf38474b5ca18043b11f11c9ee9eb0cacc2ffb740e5a57c12" - deposit_count: 375 - execution_block_hash: "0xafa631155e92b5e36aa357fdcb2c59894f96d85f0cd7e7704da84c28de104088" - execution_block_height: 376 -- deposit_data: - pubkey: "0x967633e5396683a4d51ac8e531194d8243fb808b3237c5ff635afcc0c6ab3fab8443cf9bc7268c25c85d5c9d3b42463c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x920074fe4578a343f3c10a152857b1301b3bd370b5fdc6e9f806f46f59360c24cc06b6736572613ec332d06032280b2117f89e75a93ce3102032f5326382113bbb300c6b3e5ce36921f661212338f5342683ede2eb3505bb4ac720cc3c60263a" - deposit_data_root: "0xfd47af7a32eeeebd5d5e99faf7988bc11d295a16351dd61802acf29bb7c7c48d" - eth1_data: - deposit_root: "0xc334c97c33e4fbbd1cb28802b5845db45ba26f0583b88374f650c08f577a4629" - deposit_count: "376" - block_hash: "0x0477978ff52a8aadcc642ebf3e523c6fab4b2be5cd8e92abaf545f1f4974aabc" - block_height: 377 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - deposit_root: "0xc334c97c33e4fbbd1cb28802b5845db45ba26f0583b88374f650c08f577a4629" - deposit_count: 376 - execution_block_hash: "0x0477978ff52a8aadcc642ebf3e523c6fab4b2be5cd8e92abaf545f1f4974aabc" - execution_block_height: 377 -- deposit_data: - pubkey: "0xb3dab04913697100af2268f8d71e4daaa6475d08785b4ab816d1a9d209703abdb3e890dcec518c88cb5983d30e977408" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x84f73f81a47107e055c2332123f547da1c2d16e3434b8f2a2cfc18d2f23c17257cd047b6bc12cef2c1e32473afa897f802ada46f50d4c910ff4048927c64c3983e42c8c5e3681da8ed296589b8481314ddf847049451cdc7e429520f2a2f5ef3" - deposit_data_root: "0x6dad28a69491a04e60893860dba1bc4be2ddc602499a2c1893fbbc9f3a24ebd6" - eth1_data: - deposit_root: "0x2817a89668f5bbc79855acf38ecd0089aebd5aed31a3a919aae56f2609d16eb4" - deposit_count: "377" - block_hash: "0x5e0a1b630591445514724260df8d60a7c36529f0d6c532b19b8115681c754df6" - block_height: 378 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - - "0x6dad28a69491a04e60893860dba1bc4be2ddc602499a2c1893fbbc9f3a24ebd6" - deposit_root: "0x2817a89668f5bbc79855acf38ecd0089aebd5aed31a3a919aae56f2609d16eb4" - deposit_count: 377 - execution_block_hash: "0x5e0a1b630591445514724260df8d60a7c36529f0d6c532b19b8115681c754df6" - execution_block_height: 378 -- deposit_data: - pubkey: "0xa4bdf84013dca6a9af8d56d0bec9767a969d7b0b88802ef0eac99a9551dcbc7e03336e63943d8cd072be2d2915d9db25" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8da17c87dc9aad2a3ac17331e00065d70a1c5351f063d22e23f9047d45938cec61ce0f0eba5966a85694dc30298fce9f1726b82286483f93c9ee867095eb398daa696e6ff40d0b525c1d249f48359d70f7f115c53f45a517d1dc1d024a40991b" - deposit_data_root: "0xd9d6dd6ace596c76d133833e081bfb09dae6f0b3fb68ff016b2c3157e19ecc16" - eth1_data: - deposit_root: "0x0c72dbb6dec2b4a4f0b06aff6b28e5d323de1238e9c38b01414d588302cf341c" - deposit_count: "378" - block_hash: "0x613eb202e2e6e48ddab9d97aa5bc39164d7dac1613890ea7840783b6abc54c58" - block_height: 379 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - - "0xe1fb9fc453a915b6bddc83453cbc120de96754c37d0aabe33eae0320527ced59" - deposit_root: "0x0c72dbb6dec2b4a4f0b06aff6b28e5d323de1238e9c38b01414d588302cf341c" - deposit_count: 378 - execution_block_hash: "0x613eb202e2e6e48ddab9d97aa5bc39164d7dac1613890ea7840783b6abc54c58" - execution_block_height: 379 -- deposit_data: - pubkey: "0xb83a5503f8875dba99bfb8077c50b56120456f836cbb8b89a24e17d88c76071ed9b08a54500ad8e24382a43fa67df89a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb5eeec2defcdfb4015ec6f688f641a597f533106ab62765585e59e86a27e807cf322008469bd8e3649fd7ae4a367ff8a05956b65081161d7f227bc72a5385696e36a77a1c9b77d4bd553be66e1166efa80b9708605f6480dbf72ffa229a43c29" - deposit_data_root: "0x174145bc6f9323ff01709c7c767c1a2f3e2608a17e8393966c64a5f1a34ad0b3" - eth1_data: - deposit_root: "0x32dc12145beb7653b4766efb4d84b1590f50be40852bb4281f0f29c0e1c5d874" - deposit_count: "379" - block_hash: "0x76901b62737505430cab3df9d5e8bcf58ec8f86c3db8c95a3cd14838a28a923b" - block_height: 380 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - - "0xe1fb9fc453a915b6bddc83453cbc120de96754c37d0aabe33eae0320527ced59" - - "0x174145bc6f9323ff01709c7c767c1a2f3e2608a17e8393966c64a5f1a34ad0b3" - deposit_root: "0x32dc12145beb7653b4766efb4d84b1590f50be40852bb4281f0f29c0e1c5d874" - deposit_count: 379 - execution_block_hash: "0x76901b62737505430cab3df9d5e8bcf58ec8f86c3db8c95a3cd14838a28a923b" - execution_block_height: 380 -- deposit_data: - pubkey: "0x83e32149769b6c9091f8921195802c8948cfd2b6c9bf9760e70641757719e028d0e65c85bd0fccbc10b81cbad1c87d9a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x93b8c60e836c5181647760ae65b7b5f14a89c2b09dbfbfd96aad5dcd2f569414e50e0008e821b9abf0b7b7e7c7e389a00b7cd596c6410826d484d31aa303a134c4f518d7351abf82d8d745944924bb3240ffa81d2bbfb4b2ebe1cc241aa9bf65" - deposit_data_root: "0x8970139619fc0b0d1fcb5f385618ce7d725d20c55c85a77aea8b749d5ae5dd33" - eth1_data: - deposit_root: "0x1fcd7cba4e2a28308ec039550a5044e24d2e76576f39e7e5d6d08325cd8f820d" - deposit_count: "380" - block_hash: "0x941b810b7df953ef1c939599bf5f91fa02b40c02ecdbc9db3b835903b85b4ee9" - block_height: 381 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - - "0x05b6bbc7f807ab0dcc2cc1033e4743bfa978b4068e735ef860846a4ef71e4ef3" - deposit_root: "0x1fcd7cba4e2a28308ec039550a5044e24d2e76576f39e7e5d6d08325cd8f820d" - deposit_count: 380 - execution_block_hash: "0x941b810b7df953ef1c939599bf5f91fa02b40c02ecdbc9db3b835903b85b4ee9" - execution_block_height: 381 -- deposit_data: - pubkey: "0xb3b60189b0b7dec69db000719fc89cc811c60e70b80d764af86a9dde1c4becba8e561d9f4ca5437481f01b0d7d1fc807" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x959a30b65aafdc7e021fecd8d5eba6e2b0a4d0dd63aff40824a6a3b9b1b63804be39acce4df08e4025ed5445c606761d0f0090182aaed4727b7d5da87b0437e3a5ff8b395e9e4d241fc3f822ce92717733c58d54172fb14e7ca07aaa85243835" - deposit_data_root: "0x4282b4b40da830d37635588ea701f4837f32e70f1a4177192b5f3f9e53991a86" - eth1_data: - deposit_root: "0x391f2ad9469d6191d978da35b42beddd2f61857f0ca40c88b395913d472c1b83" - deposit_count: "381" - block_hash: "0xdc138b7ce041cc906320c6e6180e0410b939b18fa470ad5531bb3eb5484dad2c" - block_height: 382 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - - "0x05b6bbc7f807ab0dcc2cc1033e4743bfa978b4068e735ef860846a4ef71e4ef3" - - "0x4282b4b40da830d37635588ea701f4837f32e70f1a4177192b5f3f9e53991a86" - deposit_root: "0x391f2ad9469d6191d978da35b42beddd2f61857f0ca40c88b395913d472c1b83" - deposit_count: 381 - execution_block_hash: "0xdc138b7ce041cc906320c6e6180e0410b939b18fa470ad5531bb3eb5484dad2c" - execution_block_height: 382 -- deposit_data: - pubkey: "0xa290656399af969e0a3c03ab8529b6b0173e40c049101115120628fc09d549256f3a358f4cd858e02a9bd025a3e01dd7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa3cc5bb444b669055ebe709357821e7e18e273b2bef53252325b7dba386016c718cf01557b29f1bb465d3fbf13f7fe1a090b1d24145d090c89e8d6a4ad67b0f23b0bb60fa897bbd65b020c97450e34d7ae4ee61e0685727fab624af4d866ecc7" - deposit_data_root: "0x1d58a6e4b2fe07612286e5452c9c0bcf810331fdcf21e40471e007178d7ec239" - eth1_data: - deposit_root: "0xf40cb049da0e82cda435267314359e780d03f4490204998272fa00a9bd6dc6b5" - deposit_count: "382" - block_hash: "0xe1768f362d8733dc9330f62b62842ae31dd708e9d58a0ef208fcb174527cddc5" - block_height: 383 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - - "0x05b6bbc7f807ab0dcc2cc1033e4743bfa978b4068e735ef860846a4ef71e4ef3" - - "0x1c38bcf92acf3077bcb07ca54de94f552d3128d9f5abc93eabaabef945993183" - deposit_root: "0xf40cb049da0e82cda435267314359e780d03f4490204998272fa00a9bd6dc6b5" - deposit_count: 382 - execution_block_hash: "0xe1768f362d8733dc9330f62b62842ae31dd708e9d58a0ef208fcb174527cddc5" - execution_block_height: 383 -- deposit_data: - pubkey: "0x8633815ef4ecf32dfd3e6221d877176ef1460f7147edae206349cc816431f1a2d60fc547e6dcf0bd31111fce6a822328" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x877082c5d8d68ce8a2547eb2fa81d42c67b1a0a0da9f62ca4f3ebb6a9f3f5853a627b3cff7e8700a801af5f81e9fd42f0f6dd0952a070a88b3c2ee73adf17e72cc8751ec6dc2f1a513bbe86e25eefd129d532e50ad77223116fb5912233bb858" - deposit_data_root: "0xc11805cef7cb401d83cfcf73cee9526e60d319d708fb6d19761dfe576352f404" - eth1_data: - deposit_root: "0xb93ebaadc7951cd5056e2f900ee21ff1b94a0ef410be2345e9daa5798ee47321" - deposit_count: "383" - block_hash: "0x12fcdcd7f54e4f71eadbf4f1c91ab1b40ef47344411169c930298858a1bca8fa" - block_height: 384 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xc9521569f6cd236e7893e6e538cfe9b294455c57d6738e0d5402dfb51e9edcfb" - - "0x2e17dc08bed4ab4138ea54b4389d7956de63422e1f873bac51997acd6fc8a70a" - - "0x7472f8762e7585ee829a64478023f69abff5f1a3c9c0b7eb37c5e0e530fd27cb" - - "0x14503e81d68b81b6107b25a5cff097ac43e0ded9988b742805a287d02c1ac8ec" - - "0x05b6bbc7f807ab0dcc2cc1033e4743bfa978b4068e735ef860846a4ef71e4ef3" - - "0x1c38bcf92acf3077bcb07ca54de94f552d3128d9f5abc93eabaabef945993183" - - "0xc11805cef7cb401d83cfcf73cee9526e60d319d708fb6d19761dfe576352f404" - deposit_root: "0xb93ebaadc7951cd5056e2f900ee21ff1b94a0ef410be2345e9daa5798ee47321" - deposit_count: 383 - execution_block_hash: "0x12fcdcd7f54e4f71eadbf4f1c91ab1b40ef47344411169c930298858a1bca8fa" - execution_block_height: 384 -- deposit_data: - pubkey: "0xad5539f19d8358478970caa4fb524b274867ca7449141a21fbd07e6b6291177200e9dca369b665e12ed40dea80b69cce" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x99788b2afa65fba3d50ac737cc372707a61a66423e03ca9a96b2bad066a3e939d61ea38ff6abcce5d89dacddde104e701318854c92d09513078171314dfd7f894e818db650e6385bf42748af1b6775d3c7f78f0e344ba1f2e7e01bd812da6c85" - deposit_data_root: "0xd381809adc653a2cd994826ee024c272920803b10751c15699eeb345a8640448" - eth1_data: - deposit_root: "0xb7dd432c5408611d1368a238e7a462ba9294b34e29406a499ead95c513360199" - deposit_count: "384" - block_hash: "0xdee05ca3f55492ca6f2e09310db9339bfe0dc2e250f129950c5a430178c04398" - block_height: 385 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - deposit_root: "0xb7dd432c5408611d1368a238e7a462ba9294b34e29406a499ead95c513360199" - deposit_count: 384 - execution_block_hash: "0xdee05ca3f55492ca6f2e09310db9339bfe0dc2e250f129950c5a430178c04398" - execution_block_height: 385 -- deposit_data: - pubkey: "0x981a72fc476e4f532a10da80f235deb940e4cb6597c8e0f868171488f0eeb5fcc988048dd9ea3bef015aaec0cd79081a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x84dd84cfaf29083c34505098112ad7f439c066376256a5869e77cb6e702070d864684ac093e37eb26759aca78505bcf5189047ded56671cb09b0eb630ffce2d6407747fbed2530efeb0dd07beab083eaed840e3e8440319f97aad670cefc83fd" - deposit_data_root: "0x298c5a5474ec0f8a9a1e151a9ee7fc14ea9597803b32884678851dab464385ba" - eth1_data: - deposit_root: "0xfc0287e35ac5abcb9e2eac1a295d56d943c303bbb69ed02009c816a1e95ea769" - deposit_count: "385" - block_hash: "0xe9257489f72b1836a2f5243c212386fe13e7f1302fde45c66355f7f053ef088d" - block_height: 386 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x298c5a5474ec0f8a9a1e151a9ee7fc14ea9597803b32884678851dab464385ba" - deposit_root: "0xfc0287e35ac5abcb9e2eac1a295d56d943c303bbb69ed02009c816a1e95ea769" - deposit_count: 385 - execution_block_hash: "0xe9257489f72b1836a2f5243c212386fe13e7f1302fde45c66355f7f053ef088d" - execution_block_height: 386 -- deposit_data: - pubkey: "0x9282d6526651dc5e7caf69877a2209d4346bb231b3caed98fec1c95ce3eed284da0617c4d1722e7c316eecebf73601f4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8a07056d6cbc4955204e276e443ff9d0b7a22247a7481efd0329dd3f40f7935a8d1a7057ae628f58e4979550025eeb310ac822934b696c3f1988586a32b3b050f4bd34a74b522fbd8e94d6ac66b0127b95d3ff2e0a4d721c2564cead9c8714ab" - deposit_data_root: "0xc39e707c0c1997a5199a59f529dac27feaa0591599dade101c6bc98e39c500d0" - eth1_data: - deposit_root: "0x531ee9c8985bee7183a5dc8fa5639fdf10c28f326426db8a657b0a5750cdbd51" - deposit_count: "386" - block_hash: "0x7f66e053d87f7f7b0687a29bcb1b42034b3a318d2918fb763a75a2f7e252cf4a" - block_height: 387 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x6eb578b1e3f55c300c0071b7f22d0ee4d4a1b7a8415e8756521f9f68caca8852" - deposit_root: "0x531ee9c8985bee7183a5dc8fa5639fdf10c28f326426db8a657b0a5750cdbd51" - deposit_count: 386 - execution_block_hash: "0x7f66e053d87f7f7b0687a29bcb1b42034b3a318d2918fb763a75a2f7e252cf4a" - execution_block_height: 387 -- deposit_data: - pubkey: "0x836f2b3e00dfa069e4f315454cca69a56a36b6d32514ac81d168e34beabc42ea8a27918d60fe14a8c93ccf040faab02d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8d45bf64b1cdfabf3d9d5f86008ed55b03b2e8ca881d85c0131ebde4834c423a8da16e0abb9e963b7b7a3b5e8229dfbe04c89313f74b9e8f319a5f27d19af4c01605129e6bad45047308c4e5d024e47f69a1f84863a770927434625853032b70" - deposit_data_root: "0xe21a86f0256ac65cb7260bbf774eafa1ca0467dacb31a1fbe35aeb795000470a" - eth1_data: - deposit_root: "0x897cea6ff0ca2f5472bbae1583e7d256c8fb1cb9729a415ed2d6fd497ecb83ec" - deposit_count: "387" - block_hash: "0x1a8b424e0faa53f76a7a87fb68d980aae685c81c9e79734f883c5a3116bfddcc" - block_height: 388 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x6eb578b1e3f55c300c0071b7f22d0ee4d4a1b7a8415e8756521f9f68caca8852" - - "0xe21a86f0256ac65cb7260bbf774eafa1ca0467dacb31a1fbe35aeb795000470a" - deposit_root: "0x897cea6ff0ca2f5472bbae1583e7d256c8fb1cb9729a415ed2d6fd497ecb83ec" - deposit_count: 387 - execution_block_hash: "0x1a8b424e0faa53f76a7a87fb68d980aae685c81c9e79734f883c5a3116bfddcc" - execution_block_height: 388 -- deposit_data: - pubkey: "0xb4d3e8864dc26809ed314a03e60c7fbe63ce8921dcb52affa19837e59639513782a1e526823f77fc9564d8fcc15bb0ba" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8768e4401b70479e0e321cf92cc78d75b597714854e2b28ebe847e50090e3a0c9dfa3e226f80b6b44e070bda975b11a10990f572268d88d9a0060051998c3151bf839bf91b05e417ce5d98978dfdaea0166cac0b3bccddbe52133c47cc5b3917" - deposit_data_root: "0x67c70be65243658ef363ce8536fd2c09c653a3cd1be0cb7673f8a103ae07b7dd" - eth1_data: - deposit_root: "0x707a55c126befe50ff91b08a62770102096b0a4c87aca8ea8821733237f9b4fa" - deposit_count: "388" - block_hash: "0x016e4d950de17f13291f14596645de6ae51e40614ce2e39c02159e18a7c9414e" - block_height: 389 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xafae78b562f726681144ff9aa2563edbda5e907e1eb32ee2babf8792cca6c552" - deposit_root: "0x707a55c126befe50ff91b08a62770102096b0a4c87aca8ea8821733237f9b4fa" - deposit_count: 388 - execution_block_hash: "0x016e4d950de17f13291f14596645de6ae51e40614ce2e39c02159e18a7c9414e" - execution_block_height: 389 -- deposit_data: - pubkey: "0xb35c3469a78ea17878cbb166377b8aaea461a0f7d58cc5f77862cb9a232c5a95452d523362808b661cd24a1e0baa67ab" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x93c7d303f1562b87c5f15c0d45026ba2f5ac22a3ccba6a96310a8962192e0577f6464223375100a8f053cb9fdc03f3e40a91ebe2014116b316a79bed10235831dad5c2886e0cee562b06f2098c70a652196cfa73585d4850f16853eac161d702" - deposit_data_root: "0xa9dbd218ea842ca62f1647881f4fc2e3bf1dd7a67a4b99eef47d43d74c34ce59" - eth1_data: - deposit_root: "0xd9d9f4dadee20fe6a1213a8e4919fe470c34b5315061c12c3326281be071b803" - deposit_count: "389" - block_hash: "0x6ebdc4ce688958aa6c4d76d6ee80bed95457f73964cbfa10b2553a44158e1a94" - block_height: 390 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xafae78b562f726681144ff9aa2563edbda5e907e1eb32ee2babf8792cca6c552" - - "0xa9dbd218ea842ca62f1647881f4fc2e3bf1dd7a67a4b99eef47d43d74c34ce59" - deposit_root: "0xd9d9f4dadee20fe6a1213a8e4919fe470c34b5315061c12c3326281be071b803" - deposit_count: 389 - execution_block_hash: "0x6ebdc4ce688958aa6c4d76d6ee80bed95457f73964cbfa10b2553a44158e1a94" - execution_block_height: 390 -- deposit_data: - pubkey: "0x800044ce43a3eec0738b16300361bc744173822642fdf7184a7ac58f6b315478c03f8b57efc36b37c262e08b66260dbc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb0515fc2450c616acbe03511afc4c02b85e0e2ca0793135a3926fa0b55665f37a181379c67f0bfbca09743436891a3a012b8d06ddbadd8ae84a7526b52168b3c19c075917482b5e6347dd134c5a4304d3d4f69310c4a51f611b61756a732acde" - deposit_data_root: "0x86f5cf1fcdedaef646198af6429d7bfd31f55700f6f9c39625d93b6de78b01bb" - eth1_data: - deposit_root: "0xa62e54acb14771006dd11ca8e80ccb43520f1921a58c955e555cef5e45750ee5" - deposit_count: "390" - block_hash: "0x1a7e29fe5e78f36f6249c8d0afb846a437d99ec96465d6c64a8bd1fdca8640c0" - block_height: 391 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xafae78b562f726681144ff9aa2563edbda5e907e1eb32ee2babf8792cca6c552" - - "0x879021b47e1a1c0942dad2c1b18300857aff607246ec88ae3ff1558391e07509" - deposit_root: "0xa62e54acb14771006dd11ca8e80ccb43520f1921a58c955e555cef5e45750ee5" - deposit_count: 390 - execution_block_hash: "0x1a7e29fe5e78f36f6249c8d0afb846a437d99ec96465d6c64a8bd1fdca8640c0" - execution_block_height: 391 -- deposit_data: - pubkey: "0xa3fe94ea52c28c985a901f8d895f8d2493dd1d939da8f775c7e595379431d557b83efce814b3f2d9b5a463c8ab5234d0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x84653812b348ba58c6eeddbd85870a0a3109da32bada6c5fb0ccb4f360c1d63866cc3548b03b066c0ff568f82b6bc4ee0e0b6665459569329878cb2a9cf5ee6169ff8b47ebefdc104e5cc3abb9caedd508bce5d02e6a5997b0f2c75f3b31781a" - deposit_data_root: "0xf4f62e401aa56826818ca1a510a840ef08f374eb55e4c193fc58c64d15dfd9b6" - eth1_data: - deposit_root: "0xbb7338e44b9b1773d9a2537d33916a39047df59cf8a83b83ee15c4ebd7061f17" - deposit_count: "391" - block_hash: "0xc54fa15d7cf7e89fe249a3d43dc9aa194d89b7ed0709a9c453d63725a36b4bb0" - block_height: 392 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xafae78b562f726681144ff9aa2563edbda5e907e1eb32ee2babf8792cca6c552" - - "0x879021b47e1a1c0942dad2c1b18300857aff607246ec88ae3ff1558391e07509" - - "0xf4f62e401aa56826818ca1a510a840ef08f374eb55e4c193fc58c64d15dfd9b6" - deposit_root: "0xbb7338e44b9b1773d9a2537d33916a39047df59cf8a83b83ee15c4ebd7061f17" - deposit_count: 391 - execution_block_hash: "0xc54fa15d7cf7e89fe249a3d43dc9aa194d89b7ed0709a9c453d63725a36b4bb0" - execution_block_height: 392 -- deposit_data: - pubkey: "0xb04bbe642a86b34459811d60affa8c72efaa288a34d4f2e5799807ced34747ef09ca47f90645d36c0a43fd9ce592e41f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x87b573600a6f5a6af8a6c8493ee98b9b179cf34aebd27ff4f063982a34464ea2faea6896f599dd885b0812f2829529e8066a5ef915c4b756ffe094a5c9e33c12ceb27daa3f272e2b8532bfda9724b799662a8c9030d4671f03224c391e283977" - deposit_data_root: "0x08525bcafc67d143769778c202899f3bef45d03b0b4c47f40782dbd2d714ecf6" - eth1_data: - deposit_root: "0xa3b155eea9ce7c9e359bdfeaaa3226882a88b472ebc2f6dc20d63249f87fc31a" - deposit_count: "392" - block_hash: "0x54ddd2ee62cb4efc55efbf284b7c86b390754e6f0ebbb8a92c3546648444f7f6" - block_height: 393 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - deposit_root: "0xa3b155eea9ce7c9e359bdfeaaa3226882a88b472ebc2f6dc20d63249f87fc31a" - deposit_count: 392 - execution_block_hash: "0x54ddd2ee62cb4efc55efbf284b7c86b390754e6f0ebbb8a92c3546648444f7f6" - execution_block_height: 393 -- deposit_data: - pubkey: "0x80609da89da3d8a011674d17aacc7378a9ed907455c914c3adfe80d9d5ff1b903cf0195f7999cd27af91de5177af295d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa38c302d9ccd055c116a22a63757dc3cf5a4dbe498beaf638f0d90446ee263292b388036081585309c41b39c6d88622819cf63b6a5fcf985d5b6c01190b5d08627bf7d13dbf0ad9bd4b2489e1f4f1eb970c1e29c9f76406e49e04275b2bc65e2" - deposit_data_root: "0x55cc13b5b52558f81535da2bf312b7430662936ea5e8f78f1042d174acfae930" - eth1_data: - deposit_root: "0xc4950b3e40fadc566118b3124e334889bd809244c4fbd2fb4e2e00dd940c6052" - deposit_count: "393" - block_hash: "0xc9e0def5a794986bc01573227a104a80465d774957b94c3fb5f66f6507226276" - block_height: 394 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - - "0x55cc13b5b52558f81535da2bf312b7430662936ea5e8f78f1042d174acfae930" - deposit_root: "0xc4950b3e40fadc566118b3124e334889bd809244c4fbd2fb4e2e00dd940c6052" - deposit_count: 393 - execution_block_hash: "0xc9e0def5a794986bc01573227a104a80465d774957b94c3fb5f66f6507226276" - execution_block_height: 394 -- deposit_data: - pubkey: "0xb929600f99e80e834dd9220a02e8b395bdd8cdfd9f93f201e6cd89ae1950e0789f93862082f0c7efd45e4b69224bd92a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x98aba9f2310ac87434cb6e273c5bf0854789024046d4eecdd20edc20d59e9c9585e3be1a9197a928444de4ed37e582720dfc1fbf5a765c7cab9bbccf5d91b27413b681e853690ebea5aa8b757f69b9c2bcb704a9bbe9667573f1820ab1ebc524" - deposit_data_root: "0xe6c9a1e53fa2bc6dfb3a0efcb15d5b61a0b00d40c05e85fdc7c73c22c0f1fb2c" - eth1_data: - deposit_root: "0xbb0d4f28737877ea198db2552a20e83fa21712d68f256fb4c47b00bc79a6e725" - deposit_count: "394" - block_hash: "0x07a4841aeadb48375c77cc7bbb85952ed2a2d8590b379cbc8296b93e5bea89fa" - block_height: 395 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - - "0x0d997180aceeb9c78bfd8dff8c8f847e603e9ef4da82207e9ef96fa26dd60c18" - deposit_root: "0xbb0d4f28737877ea198db2552a20e83fa21712d68f256fb4c47b00bc79a6e725" - deposit_count: 394 - execution_block_hash: "0x07a4841aeadb48375c77cc7bbb85952ed2a2d8590b379cbc8296b93e5bea89fa" - execution_block_height: 395 -- deposit_data: - pubkey: "0x9052bae965368c78db0d638faf2ea9beb0e42b469adabbba09f85936f948a1cd77cf811a20ae759498770855068adae3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8fd3066f0eb12e45912dad9e0f03999efd048f5c3a7cd0ef0c1b28469d734e0900faa8208d9f3c2ea4475c4b0edef88f01a0b0c265ff4adda81d58422b1512db9e12fadc2ee56a87d17f98cb1d403321af314954be68de1ef1626645c9d0b58b" - deposit_data_root: "0x8041edc01aaaba30de8f64e99b463e7884fe5d7927a8e1ac0f8c26e4eeefac63" - eth1_data: - deposit_root: "0x159cb3175d894680b3f9885f0bd4fdf130731ae6fbed0bb8ef6c8ae22d3c141b" - deposit_count: "395" - block_hash: "0x6f29a9e99967a5b3ae5da3eca55e429b68e12c0a3ccf3765743baef29c1509aa" - block_height: 396 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - - "0x0d997180aceeb9c78bfd8dff8c8f847e603e9ef4da82207e9ef96fa26dd60c18" - - "0x8041edc01aaaba30de8f64e99b463e7884fe5d7927a8e1ac0f8c26e4eeefac63" - deposit_root: "0x159cb3175d894680b3f9885f0bd4fdf130731ae6fbed0bb8ef6c8ae22d3c141b" - deposit_count: 395 - execution_block_hash: "0x6f29a9e99967a5b3ae5da3eca55e429b68e12c0a3ccf3765743baef29c1509aa" - execution_block_height: 396 -- deposit_data: - pubkey: "0xa7c693916f59a0ee41b13d045632d4c5e8703a0e1431b50ba0450834747e41a6634ab9c0c1efa6af140f2072c29cf7a6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2b6db61ed48e65d9b02339ec3dce5b65082ddb6e88f2a9d68bb008213c2f413e884df2e36a2df4ef4872a1d7918d8d60b4b0cecf20345016d9fb90f823950b40b56686cfd73a60f366e89c0664b512fa6b1fa7e03d10407e5391a01a793f180" - deposit_data_root: "0x58e50fcd08de046408f42ef030f88327a7090e3f28d86b90c33226f625d8591d" - eth1_data: - deposit_root: "0x5659e739fa58f5bac4607770ad5735be507757cc931e78a32971dba1062f9f79" - deposit_count: "396" - block_hash: "0x98492c4ba8ab648f0b7347fd237f6c97e28f8c9276202b7ceeceb4e2b55a4caf" - block_height: 397 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - - "0xf317c693e0d6b8255336757d18a3edf61408bfb581694def9ea146abfe6ee676" - deposit_root: "0x5659e739fa58f5bac4607770ad5735be507757cc931e78a32971dba1062f9f79" - deposit_count: 396 - execution_block_hash: "0x98492c4ba8ab648f0b7347fd237f6c97e28f8c9276202b7ceeceb4e2b55a4caf" - execution_block_height: 397 -- deposit_data: - pubkey: "0x83a40895d9005f007df9ed7fdb17d820d3d66fe419480cede7518e1f54e4c1e2a97ffd71fe2f01187c3707cde5bf9915" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96d6a5708588333bbad7bf98de64425be3f93c823588a7f30a2391d2fea7a68d4d46572edf9b5884a3669648b6233f970049e50e232746643e3e7edbdc733853ecff22492cb6d7befaf83f4f9331a9d8f1b4bcb7e28f2d1bdfd94903f7c121ba" - deposit_data_root: "0x4b89dcec503cf535c9cb9f034604ad431a3baa5f8ad939a9fee0f17a518c58e7" - eth1_data: - deposit_root: "0xfe07bcc8cf3812b6959665a4d69ac7dc10210db6de6b5cb3cc3ab4e3731a553c" - deposit_count: "397" - block_hash: "0x9adc7e62cd440bc4c77546efdeb7d185348964c8b7d899634d8c442ae02f0cf3" - block_height: 398 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - - "0xf317c693e0d6b8255336757d18a3edf61408bfb581694def9ea146abfe6ee676" - - "0x4b89dcec503cf535c9cb9f034604ad431a3baa5f8ad939a9fee0f17a518c58e7" - deposit_root: "0xfe07bcc8cf3812b6959665a4d69ac7dc10210db6de6b5cb3cc3ab4e3731a553c" - deposit_count: 397 - execution_block_hash: "0x9adc7e62cd440bc4c77546efdeb7d185348964c8b7d899634d8c442ae02f0cf3" - execution_block_height: 398 -- deposit_data: - pubkey: "0x8ee8e926da165b8f34d249d194864a3994278aa4b829295480edac18395167fa016b2a4ffb0b0f3c89d18622bec2409c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96e64eb4225338a95db3620d53f114ce50c67c76f2775479d43b43dc99cc49203bf4cdb0a2f32ae8bc38871d5ea159f71573f7f17cff542489bcf03f71757e447cb1d3a88fbcb1849d406547f2ea18acdda8999dec66c611c66572435d27843b" - deposit_data_root: "0x6f8c0bddde78f7897c2d2fa74c01ad7d54767bad64e1a110cecd9a13ffb11f31" - eth1_data: - deposit_root: "0xb10c6eb035b9b3ceaf4c5fabef893bc0e20d0fc473677948cab48835462cc8eb" - deposit_count: "398" - block_hash: "0x1aafe4f6b5beb8fefa9b9f17b11e8c330f1485d6b915bba56cd48ee2fd300ffa" - block_height: 399 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - - "0xf317c693e0d6b8255336757d18a3edf61408bfb581694def9ea146abfe6ee676" - - "0x2a66533bacc64203fa52d8f62fd219a28a45bcbb7c0e29db839a9d9b19a5f91a" - deposit_root: "0xb10c6eb035b9b3ceaf4c5fabef893bc0e20d0fc473677948cab48835462cc8eb" - deposit_count: 398 - execution_block_hash: "0x1aafe4f6b5beb8fefa9b9f17b11e8c330f1485d6b915bba56cd48ee2fd300ffa" - execution_block_height: 399 -- deposit_data: - pubkey: "0xadd60d6bf2abc6935ffebfe463c9b305ed2c898a00e2a84b997c014e140a7b254dfddc5be39e406ef0a893c4d67dd1ba" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x956032f3c008672890406790f1f6df66918c5b34b5c99bdd3e9e573b5c9eeb38457dad357d91bf63a5b40712f033196717c67639a675074a273d807ed38cfff57d5ae0a5da85425cc5e09a4fba69ef1ac8fde490e2acfcccf84593275ac13b6e" - deposit_data_root: "0x9af6c4251177b012f498c5a6444e89a07f3b1f99c0c47c4370943fb4a19875a8" - eth1_data: - deposit_root: "0x2986d1af8658f87aab3fc101470d46c5875124b69e93da963cd7ed8a0815db27" - deposit_count: "399" - block_hash: "0x3f9fb82bf76f4d04409151836ea7e82696193cab3ba0a6505f8fea426708f118" - block_height: 400 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x7e5837337e12008bc00095f95c4579a110096a154443242630b70a200c8eb25d" - - "0xf317c693e0d6b8255336757d18a3edf61408bfb581694def9ea146abfe6ee676" - - "0x2a66533bacc64203fa52d8f62fd219a28a45bcbb7c0e29db839a9d9b19a5f91a" - - "0x9af6c4251177b012f498c5a6444e89a07f3b1f99c0c47c4370943fb4a19875a8" - deposit_root: "0x2986d1af8658f87aab3fc101470d46c5875124b69e93da963cd7ed8a0815db27" - deposit_count: 399 - execution_block_hash: "0x3f9fb82bf76f4d04409151836ea7e82696193cab3ba0a6505f8fea426708f118" - execution_block_height: 400 -- deposit_data: - pubkey: "0x8f48795cda6795e9af514489989a9f9e8b6db2b52a36881e4ba166327153d6db922e5148db044b27efe1831b54e16270" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89b774642bd7faeefc6435c7184df11dd2fb34464f7ece0430b69d220032f77eccf6187839c5d7e233d463b48640bba211cd063cc9b638a954ea2c782919656980c7491ca632a83538146d9e6156ddf6f7260fb5ad1b4756549ba7a1b1f2cc1b" - deposit_data_root: "0x70721ee8848f77b3243c3ea7b1193970abadc48446a2f47116cc9371f55745e7" - eth1_data: - deposit_root: "0x452498ecbea92a19db703d3e4544acfa45d9890b43883f5f88081533a826f236" - deposit_count: "400" - block_hash: "0x604f66f12e19e8be5538632835dc419e722d35e3e15b6bc7ba305348680a4ec9" - block_height: 401 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - deposit_root: "0x452498ecbea92a19db703d3e4544acfa45d9890b43883f5f88081533a826f236" - deposit_count: 400 - execution_block_hash: "0x604f66f12e19e8be5538632835dc419e722d35e3e15b6bc7ba305348680a4ec9" - execution_block_height: 401 -- deposit_data: - pubkey: "0xb43ccddb32f15baaa32d69abf4b298d38e835607fd74aac5b25b5b161e8c865744a06526594faf39521e51fe44d66658" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb35daff1750c7ab2ff3ec29d401f83dfcc035bd5e0de5e45bc7ef789558349839da1c3588a263359ba86b4812b6c28360c960dd5aa762c7ef6123dc3e2ce63064f9ef2ed74eb9efbdbda96d87cfc2b50f4947e66ecd0244c546b9450277e6861" - deposit_data_root: "0x0f94bc880af7f796e05590236751522502362f586d3bcf4d42799a6931676193" - eth1_data: - deposit_root: "0x9c53aafb0bf05b211e3d12a2771506145ca19774733164e549865501fb0f26a9" - deposit_count: "401" - block_hash: "0x897487f0b6c2ee25dee391c9b855ac06fcbbcf4f8ced76cceda3081c27d461bc" - block_height: 402 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0f94bc880af7f796e05590236751522502362f586d3bcf4d42799a6931676193" - deposit_root: "0x9c53aafb0bf05b211e3d12a2771506145ca19774733164e549865501fb0f26a9" - deposit_count: 401 - execution_block_hash: "0x897487f0b6c2ee25dee391c9b855ac06fcbbcf4f8ced76cceda3081c27d461bc" - execution_block_height: 402 -- deposit_data: - pubkey: "0x83249b11efc76fba2ab77df9b5ffc607e6e2265c7f761dbb3099f3b4ff74ad8b9570036cb04942898f8c33b8442adfcc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa7e93b3bc24dddcd889bcb9d313f632eeeea863ac36f6bf45cdf2aa3817eea3d58f8e133f3d7c52318370ca3d51458e3195aaf7dfb8affbf789e294893b93e1420a5417e2bfd42ef06d3a0a93d35e6ae4a89aa4a3887c53fecc5a5784951a7b8" - deposit_data_root: "0xe1f17fc61185cdbeb20440550c2bbee0b02afd1bbcdda44938f893fe92e6d2ed" - eth1_data: - deposit_root: "0x636af0f0424726b4060828f72a3186c6b7dfaaea3591c5e7691d3a62fe183a7f" - deposit_count: "402" - block_hash: "0x6cae019fcf9937f0efea9dc1ab3a9550aaf34af27f8e63b644f9329e61176a02" - block_height: 403 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0xd120ef8988987a3de5b82d1b73dbf94fd20b9ab5bf7b3297255bc0ea59ffcd70" - deposit_root: "0x636af0f0424726b4060828f72a3186c6b7dfaaea3591c5e7691d3a62fe183a7f" - deposit_count: 402 - execution_block_hash: "0x6cae019fcf9937f0efea9dc1ab3a9550aaf34af27f8e63b644f9329e61176a02" - execution_block_height: 403 -- deposit_data: - pubkey: "0xb049dc45fd585ef8bc67831dc47690f95e466cf11127def6ef9fdfc43017518752aff3df93c3e5f68083f590f554f3dc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9411afc1b608ca75c7e4af02d1688588ca24b611b9b7250ee87dc7504f1c60543932bf50745b64c363537ce553234a62195d605f970744979913756525764b61e274665c5811d56b13928fb1bee16a35269bc2a466726ddbff0ebde348680e50" - deposit_data_root: "0x11af26393dac3ce8fb05c12d6ece2cfceec9489fd2d915646bb4840993138571" - eth1_data: - deposit_root: "0x9c778cae240d62b556752b616662c7ac895a162e9a7730ff0d52b82bf469883c" - deposit_count: "403" - block_hash: "0x40daf225c72e6c2923e9ad5544c472bed26439a283682bb93688fe3d88d2e76d" - block_height: 404 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0xd120ef8988987a3de5b82d1b73dbf94fd20b9ab5bf7b3297255bc0ea59ffcd70" - - "0x11af26393dac3ce8fb05c12d6ece2cfceec9489fd2d915646bb4840993138571" - deposit_root: "0x9c778cae240d62b556752b616662c7ac895a162e9a7730ff0d52b82bf469883c" - deposit_count: 403 - execution_block_hash: "0x40daf225c72e6c2923e9ad5544c472bed26439a283682bb93688fe3d88d2e76d" - execution_block_height: 404 -- deposit_data: - pubkey: "0xb0f4dcd488f6725946e1a384d9ae9cd6a12824dafb426e15f4b848b6dabd46ec1177fccd0fa5c0c2260fc57aaadf1e7d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x91ca8e45fc3d7c4003d0ec4602231b3f86e119931a1c8d605b86d36191a2d32193bd10741920bff28e2d2d2964f01b9c034ddaf18f30d326b6f91be845c9c75ef0201cd50ff50f39c29430b7492a1e4537926c786525091e1c2c238ad4698b49" - deposit_data_root: "0x686a7b22bebf76249ffc853c5122014578f3cb65dd2c6b70909ffce37e88150d" - eth1_data: - deposit_root: "0x2a08f81dd020438f364a87bf7dab7624c404e0934662a5588c924c5fa10abab1" - deposit_count: "404" - block_hash: "0xd4e4fc76c0e27a2f700ade91f2510b65de7045289c1c22f964ea29e2a7dca632" - block_height: 405 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x84a7921ef234cafc908a09e2bbbdd215ec6e0c4aa800834f18fd8592b86fb2cc" - deposit_root: "0x2a08f81dd020438f364a87bf7dab7624c404e0934662a5588c924c5fa10abab1" - deposit_count: 404 - execution_block_hash: "0xd4e4fc76c0e27a2f700ade91f2510b65de7045289c1c22f964ea29e2a7dca632" - execution_block_height: 405 -- deposit_data: - pubkey: "0xb400b2f5705dbd2905c5d9fe3b2f29cedb380954a60b4fab0e55009c47e1908a30bf286f7e01b7125985c9007015f37c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb8303c6fe8a1f35b218d48f838cd459dee1422b6f202c1b710c0179817f1085c8556cb71e6b0a97ad0484349fe854f6b0d4da3f69c0e3d5708843d4c0ad4d3f2bdc1cdb40db8a654a1f708b4318274ea613e75be4d0f6c9a26a1d52d950c1f5a" - deposit_data_root: "0x54285447a3b3f65166091d937d7557f012b904d001e24cc43c8f0899522bab1a" - eth1_data: - deposit_root: "0x2964d0b869c2a6da631d3e589eca0125be3775f099f3cd1e6b7013ae66384abd" - deposit_count: "405" - block_hash: "0x227229f14dc54a592bdd03b2d84df40d64d0e6f51dcdfdf1c42ff87a1291c6a3" - block_height: 406 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x84a7921ef234cafc908a09e2bbbdd215ec6e0c4aa800834f18fd8592b86fb2cc" - - "0x54285447a3b3f65166091d937d7557f012b904d001e24cc43c8f0899522bab1a" - deposit_root: "0x2964d0b869c2a6da631d3e589eca0125be3775f099f3cd1e6b7013ae66384abd" - deposit_count: 405 - execution_block_hash: "0x227229f14dc54a592bdd03b2d84df40d64d0e6f51dcdfdf1c42ff87a1291c6a3" - execution_block_height: 406 -- deposit_data: - pubkey: "0x97580566e3ddcde5b3e0cedf688aede17a8b738a21f96a0c2efff77061e232ad877e6bc71649d0fbb3ec0dde584bb8e8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb680f12cff8dff209469f418f731a144cade0471dff2ee7b656baa3d0b2ada286bafcf1487ec3ec47b1fa83823fc237211352f5d3c28842d50124206019b16a9f7baf7c22405fbfcb49123aec9289fe8a2a6d894634a936297f0bd08d591074c" - deposit_data_root: "0x5db3228f5c2c952907c045dbb394ae8e84db47a0e25ffa40de6b58e9c6ab257c" - eth1_data: - deposit_root: "0x056d0f3598f7c612c9d73dd3adcb0dc54a8861b4c3df6b4df8ff7511a3966207" - deposit_count: "406" - block_hash: "0x5b1c9467bdfcf4b7555e748b80e684419a23798fa52716ac92aaa14186dd85b2" - block_height: 407 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x84a7921ef234cafc908a09e2bbbdd215ec6e0c4aa800834f18fd8592b86fb2cc" - - "0x85117723172be560339b88739cf982d8b35cb46f757778b547dbc224ef314c78" - deposit_root: "0x056d0f3598f7c612c9d73dd3adcb0dc54a8861b4c3df6b4df8ff7511a3966207" - deposit_count: 406 - execution_block_hash: "0x5b1c9467bdfcf4b7555e748b80e684419a23798fa52716ac92aaa14186dd85b2" - execution_block_height: 407 -- deposit_data: - pubkey: "0x9504350e6a3ad1a5f711d9e5904bd25b1f6571bcaa66403f1da9fc8200ebd8073308dcf462371073bd8b076fd25df949" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x97cf502d6ce9a1a4dadfa044a55404625dce825856a24d235e58e31726d6c656c6848a4f614289ffdf2ce52b065780a60f2ed591833b178369bf7b84e6d7f0a5bba85e79c5a37f7d7087b54f12f78dc94fedb49668d62cf052d3d36ba14cfc6a" - deposit_data_root: "0x1a6a9e33d429de9d71c1f84e4476edfea7c21cabaaff5663bf0737e75d503644" - eth1_data: - deposit_root: "0x45ce45d9a35f5bacc28372385bd446592e1198cd983a9cabf423ae68844aca63" - deposit_count: "407" - block_hash: "0xe25780387a9c484c23c6252bd8518d02fca68bbef3de900bb192b8bda868017f" - block_height: 408 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x84a7921ef234cafc908a09e2bbbdd215ec6e0c4aa800834f18fd8592b86fb2cc" - - "0x85117723172be560339b88739cf982d8b35cb46f757778b547dbc224ef314c78" - - "0x1a6a9e33d429de9d71c1f84e4476edfea7c21cabaaff5663bf0737e75d503644" - deposit_root: "0x45ce45d9a35f5bacc28372385bd446592e1198cd983a9cabf423ae68844aca63" - deposit_count: 407 - execution_block_hash: "0xe25780387a9c484c23c6252bd8518d02fca68bbef3de900bb192b8bda868017f" - execution_block_height: 408 -- deposit_data: - pubkey: "0x94184bf24da31a8ca6561c17aa36b6933f07d4966063972bfcc6c97d8daeefd05b7c22d41fa5133327cc0dd000139175" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2147f430cef90e44d212f911fb86e018b4757593fc5830f5df7c227d66c3f9498ea97f5d74fad123e7a1485589d17d90297d6203e5a815a8e154d548daa55b9f13901bc297b8fd4e6ccb909dbc8fd2f93a889d64186fc2f9a4be67496d97b61" - deposit_data_root: "0x687e4fa71fd82af6ccca467fb6b5e48f142ad7fc09fd986e2d2c12bc08e2e318" - eth1_data: - deposit_root: "0x85996ca4642a553cc8fcf6d1c797c38df7fa374a2a8a420064114c4b7b576779" - deposit_count: "408" - block_hash: "0xa1d4a35fa123ddccc268e408c9c03c5351b323e42eeae7b8415b67725957f8dd" - block_height: 409 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - deposit_root: "0x85996ca4642a553cc8fcf6d1c797c38df7fa374a2a8a420064114c4b7b576779" - deposit_count: 408 - execution_block_hash: "0xa1d4a35fa123ddccc268e408c9c03c5351b323e42eeae7b8415b67725957f8dd" - execution_block_height: 409 -- deposit_data: - pubkey: "0xb88add150c36e8c338f7ff8efa8efb4e302a5a01afb8e331282eb075ffc3f2f5e5e783a4cef6a0d9c65dbedea48cc093" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xadc8bcc68efed61ce4039fba63c806c6e59282810427369f0967c765e052048304bcb76104660d30ca2667f406c445160240514f7b07c762a83e1f7a27e5cb7b4fc81b40b5a4f8d24fb3cb1640129132578819e404da56557e5f6988e8064708" - deposit_data_root: "0x99422ecd07eaabd6d3d3380ff689d77656e191b82317f01a8ca95cbc98210d4c" - eth1_data: - deposit_root: "0x8d152a557390c7b500d1a4eda65c8491268e58d25a936cb8e032eb06be13de35" - deposit_count: "409" - block_hash: "0x864f7b84cd151b4359c1767fd03f15e94769a4a96b65a3e299b7984d6cd397e6" - block_height: 410 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - - "0x99422ecd07eaabd6d3d3380ff689d77656e191b82317f01a8ca95cbc98210d4c" - deposit_root: "0x8d152a557390c7b500d1a4eda65c8491268e58d25a936cb8e032eb06be13de35" - deposit_count: 409 - execution_block_hash: "0x864f7b84cd151b4359c1767fd03f15e94769a4a96b65a3e299b7984d6cd397e6" - execution_block_height: 410 -- deposit_data: - pubkey: "0xb25827b6746a0ed3bf133beb7e465ba08c39e1960c4555a81384502d6c244bdd7ca19f521c6d192436d71dfafa64b4a6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96aa871044bff32644f18d02e67e8dc0755621ed06eed2928626f653aa927e8976f39447954e3e0737533488633a319f16e573f70137eb877bc792b12df25b72847efa7c334309070109492bdd07b6f12641ef30895d71503755f6bd186a373d" - deposit_data_root: "0xbe183759a8073454fc423aba202855bb3537be2a6772f75617206143e492583c" - eth1_data: - deposit_root: "0x3e6105f13f3575420640b86bf5967844ef363dbd91cd6d2f3d7b55bb2cafa1cb" - deposit_count: "410" - block_hash: "0xf59f3ccc775a6fd25443c8bd4d39aee5653b9399d8db7028a1244b2b36fa601a" - block_height: 411 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - - "0x8938612c24e370ad407b03900d58216a3677570a6393d86c1199e168ac50d0f4" - deposit_root: "0x3e6105f13f3575420640b86bf5967844ef363dbd91cd6d2f3d7b55bb2cafa1cb" - deposit_count: 410 - execution_block_hash: "0xf59f3ccc775a6fd25443c8bd4d39aee5653b9399d8db7028a1244b2b36fa601a" - execution_block_height: 411 -- deposit_data: - pubkey: "0xb5ed6e77bd6f1661a5ab3f2e13743385c7ee99dc240e9575b3e7639b4f73a017cf4edbd0932e02577acd2bec274be3fc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9304d4d21d94213e2e17afba7d41167249086d34837591a7ebaa998ad31dc2085f1f11cfc01b9a0651dc5bbc4785a9f6084549527e1d9f4a256d9d6b73a1cebcbd10c9e88984acc11ca85eb8d5ff5b53cecb6bc4a366bb5cc3a82db259accf9d" - deposit_data_root: "0x56410aabb7658f5072e9d3caaafc6d06931ecd574f6af0b0c6780765b81b2efe" - eth1_data: - deposit_root: "0xf81fa25a4547d3ea55b14e7ac66c2e4d9f96c32c843d3ccf32efc7e625142204" - deposit_count: "411" - block_hash: "0x2ed4ba87f94e8bf64d8fa37d0b7cb7a67eeddfd408a8838ee62cdf8b63e36533" - block_height: 412 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - - "0x8938612c24e370ad407b03900d58216a3677570a6393d86c1199e168ac50d0f4" - - "0x56410aabb7658f5072e9d3caaafc6d06931ecd574f6af0b0c6780765b81b2efe" - deposit_root: "0xf81fa25a4547d3ea55b14e7ac66c2e4d9f96c32c843d3ccf32efc7e625142204" - deposit_count: 411 - execution_block_hash: "0x2ed4ba87f94e8bf64d8fa37d0b7cb7a67eeddfd408a8838ee62cdf8b63e36533" - execution_block_height: 412 -- deposit_data: - pubkey: "0xa25579ff3efe83b344ac7ca26d45d5aac4fd4fdb1809f20e4e48026e154e95d7ea8c7abf6e91628f8448a6a798dd07f3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8915d4d355035a608f37b9c2402a9c3b95ef8c68209b0607cc8bcdc4f10a47a12b0708da53442b82348470e179f128bb02192b872b8ea16d388ed77ac173e4b9ae9456734fc33cc05b6bce98dbcb8f69bfae7583286ed548b4711916eb059055" - deposit_data_root: "0xb55d26ad397fe717b187f5c40e5103bee6c3ba6d8a2b0d73a357f950fd172c34" - eth1_data: - deposit_root: "0x1d2b4ebde6a17b6badc11d3f4a4d67e5938f2b536fed42e015b6ae501b88bbe9" - deposit_count: "412" - block_hash: "0x616b1268dbde68f7f9ba43b43cbd7f721c94d1ae793f0101334af4ad8d8e82ec" - block_height: 413 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - - "0xa1c5ec665c8d856db67a8496e9dc6082278b339bf70b3a4f35f6d79670fa00e7" - deposit_root: "0x1d2b4ebde6a17b6badc11d3f4a4d67e5938f2b536fed42e015b6ae501b88bbe9" - deposit_count: 412 - execution_block_hash: "0x616b1268dbde68f7f9ba43b43cbd7f721c94d1ae793f0101334af4ad8d8e82ec" - execution_block_height: 413 -- deposit_data: - pubkey: "0x81dc3529a283023f70fec5eb65ef8e3b394b0239bfe40095574a881c5dbe536a6b119e6cc95668b62d6e5944386c07ca" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89053f18cbf904592529a1710bd771229ebf0709d2c637684eae158bdd5821469dcbe1263d3649a8e04b92d2d7e326ba1895598c591a69fd1048f58d2d34c1cc432dab87f8f0bed2afee9500f92ea000462b9ffb80f5ca409e0d05ab8b821aca" - deposit_data_root: "0x315194fcfff6c0dc8855f81729f8993f67b38330dbec183119694e330fec78af" - eth1_data: - deposit_root: "0x3801e05532040093e13e5b25b9afb7ebc97618181c41afec4e929e7cf8458a24" - deposit_count: "413" - block_hash: "0xc49c428e319c98556fa261f9a6a3184d5149ea8cb773b47fef238c2f8d696641" - block_height: 414 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - - "0xa1c5ec665c8d856db67a8496e9dc6082278b339bf70b3a4f35f6d79670fa00e7" - - "0x315194fcfff6c0dc8855f81729f8993f67b38330dbec183119694e330fec78af" - deposit_root: "0x3801e05532040093e13e5b25b9afb7ebc97618181c41afec4e929e7cf8458a24" - deposit_count: 413 - execution_block_hash: "0xc49c428e319c98556fa261f9a6a3184d5149ea8cb773b47fef238c2f8d696641" - execution_block_height: 414 -- deposit_data: - pubkey: "0x93da5217b698dcee39d36c25bd79f2a152e5053add9e4b17cf60cc5dadc0e2d1713d05f7b5917840b6748d25d68f4878" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaeebc4af4e5b90582f890827b760f39de6f9adf57afe53e74fa7dc7718e1a1ff4f70eb119c2f1042ee9d06264c07347b0d052699ba9145d7c3e38ffbc2d23d75f15c169b88e35810317e80281889f5a86248d05110ac7ce727996de71967cb29" - deposit_data_root: "0x38ae0e785551cf674706d6ec349cb2a8061d5a6175b87857f9c9a9f51631cd71" - eth1_data: - deposit_root: "0x64149d892f598e8b77cb82d9ec6795bb77780c16ec7519ba12d9a3dce230179c" - deposit_count: "414" - block_hash: "0xa79d343f7dca460ec92a84fe1186ecbee2f9bef1270a4cf1999cf944a86f85ae" - block_height: 415 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - - "0xa1c5ec665c8d856db67a8496e9dc6082278b339bf70b3a4f35f6d79670fa00e7" - - "0x48e639af938e1404ea96c354be4721b397a5acce7ffa49d37f8573ee8158a522" - deposit_root: "0x64149d892f598e8b77cb82d9ec6795bb77780c16ec7519ba12d9a3dce230179c" - deposit_count: 414 - execution_block_hash: "0xa79d343f7dca460ec92a84fe1186ecbee2f9bef1270a4cf1999cf944a86f85ae" - execution_block_height: 415 -- deposit_data: - pubkey: "0xb54437d1547e9a87476f03c094c86465f36376c09ef3c8fba0c8906fdeb0421d523a20ab6ce6ee18da9fbab39ede3bee" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9334c266b645d6cc2dc62f8dd0e6ec5224dacee0db27c912db0b41422bcbc20f85589fb41bfd51d22ee7030c75b52c6c0a54185c8385f95a36e97d241f32f0b903caaaea7e2cf27ec93870c1264f2ed07d73e84fac7402f03ebf77b9b5464321" - deposit_data_root: "0xaf029eb95db881e01d26610b29fb72f3e787f515905568178e89caadffceafe5" - eth1_data: - deposit_root: "0x3f34a92f21f233bbf81b0c43288483f2e79ce0d95351eecc830a97b3dcec3eac" - deposit_count: "415" - block_hash: "0x455ef1eda7e29f3ed4bbb1c6d34330abd172c4626032644f0242b4d4defa1835" - block_height: 416 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xec52edc11cb65e2c16d2dfd0ab3223c608e768e70c9e808e0a0196c82ad1736f" - - "0x0901c3b3e1bacfae214532d21ac897c470123fae6d333b9af964c91ea57d286b" - - "0xa1c5ec665c8d856db67a8496e9dc6082278b339bf70b3a4f35f6d79670fa00e7" - - "0x48e639af938e1404ea96c354be4721b397a5acce7ffa49d37f8573ee8158a522" - - "0xaf029eb95db881e01d26610b29fb72f3e787f515905568178e89caadffceafe5" - deposit_root: "0x3f34a92f21f233bbf81b0c43288483f2e79ce0d95351eecc830a97b3dcec3eac" - deposit_count: 415 - execution_block_hash: "0x455ef1eda7e29f3ed4bbb1c6d34330abd172c4626032644f0242b4d4defa1835" - execution_block_height: 416 -- deposit_data: - pubkey: "0xaa5367fff8db3ba5017bc601e9f9ceb867d54c3b17ade967b0bc905f644f6aadc8aa53d3e9ed0e278bf3fc2b0b56a7b2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b8a5f542223fdd7170978a36e904c31ec430dd68cd608a3aaac5b6514f1e817e1bb2374aa7359e85d0c280f17c99daa0049c57e853088b8ae3a23d77911c12d6094558aec8dff5a9dc917d7799e6a86d06bb54b394473fd4fdcd273868581f3" - deposit_data_root: "0xd202196054c6e1cfa6f16d82399ef65d80f0765564b15c5111da4720bec9b495" - eth1_data: - deposit_root: "0x2bbfa716fadba6b4b1d4d2b4cbd5b031bad662ad0a18d60f8fae3a9ff2238c8f" - deposit_count: "416" - block_hash: "0x376a52185e4457c7d3536a168e21870ecbb58babc27d4a74189316e089e4a69c" - block_height: 417 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - deposit_root: "0x2bbfa716fadba6b4b1d4d2b4cbd5b031bad662ad0a18d60f8fae3a9ff2238c8f" - deposit_count: 416 - execution_block_hash: "0x376a52185e4457c7d3536a168e21870ecbb58babc27d4a74189316e089e4a69c" - execution_block_height: 417 -- deposit_data: - pubkey: "0x874305f7d11e69b74164cad10ab88ea7df22208c954fcb6872f34a5f9c8292341f85a6250716270563a19b5a112761d4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xae8bde36d9ebdb5a259f005703d09b72d1828bb4b4dffbab5e145f1bc0cc857b24ba1365ead8ce0953aa67e3afa4c9a40fc79fc0da93c7a5b350e0c528f91fe7a1b124c27d0d622711be2a85abe3078010c584576b919e438721dcfe7a5f6d1c" - deposit_data_root: "0x0e431cf7531088ea8e7aa4b2ad1e3a2971548ab2fc6967219a91861599293180" - eth1_data: - deposit_root: "0x0cc28d5ecfca85f1d141d409373c12e5ef93e7fab9d3d667737b8e3e8b9d567d" - deposit_count: "417" - block_hash: "0xcc9737753a8b74792a5df80adaa5eeeb97c6d8656eaee16fcfb04a63bddd791a" - block_height: 418 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x0e431cf7531088ea8e7aa4b2ad1e3a2971548ab2fc6967219a91861599293180" - deposit_root: "0x0cc28d5ecfca85f1d141d409373c12e5ef93e7fab9d3d667737b8e3e8b9d567d" - deposit_count: 417 - execution_block_hash: "0xcc9737753a8b74792a5df80adaa5eeeb97c6d8656eaee16fcfb04a63bddd791a" - execution_block_height: 418 -- deposit_data: - pubkey: "0x96917c5ff8bd2debf697fbc64455d19cf1efc4e1327b3dde2eef7db7cab762effb43025c4162db0008a0b43b4d060748" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa16d8a141171087108c9f042e0915736a0eb801ccaf8b889a124bd6d53eaffef6518a982b0214e9b6c9a95860d5a2da50f5b3eef099489a927545bbbba3d9e0126f7720c367e93bbb64353d8b7b86363792c35e1d812fedc3495921910051be7" - deposit_data_root: "0x3dab6d15efd096307e6d2f0f3465f10a35147b60564a52e9a69c2a9d20c16a2d" - eth1_data: - deposit_root: "0xcb8c6981b53a98be40422412d265fd54abc7329f49ba6028981465210544c9c0" - deposit_count: "418" - block_hash: "0x3fb793716ced3b8a456415e801d6e847ee18b269ae9600bb2b42d698d23c6d17" - block_height: 419 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x696db144fa18876c4f737f199bf14a3d8922bd7a5c7469f783cd255b766ac02b" - deposit_root: "0xcb8c6981b53a98be40422412d265fd54abc7329f49ba6028981465210544c9c0" - deposit_count: 418 - execution_block_hash: "0x3fb793716ced3b8a456415e801d6e847ee18b269ae9600bb2b42d698d23c6d17" - execution_block_height: 419 -- deposit_data: - pubkey: "0xae77044f5f689ef3c59fbb1afc7fa2e33c5406121f9c2eb1dd089cb320b9d82ab51d6fadb545327dfcc2688c201e9e07" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2229d26b8942b36b25e0231246148d635c4abf51fbab950efea2e4f0e4a9b7f6f9ee366636bc42801930330fd7b197a11cac3cec7abfccfa29323a2810f2d209ba953a45bf11058bf438d66cec626c4c6be647cc6c0c88fe372e333863d5766" - deposit_data_root: "0xb1532d97dbb8c85040e1ebc08649670b8980fd97cae2e23ab7f1b5af06c1f597" - eth1_data: - deposit_root: "0x76d0915685d7293f2ad3c227cfd6208d198fc11d6eb154dca9ebf9f08b57a863" - deposit_count: "419" - block_hash: "0x08a0fbfdd1c3183824d6f51a123e89b1a8dd1c208dea3a167bb8c47ff7697390" - block_height: 420 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x696db144fa18876c4f737f199bf14a3d8922bd7a5c7469f783cd255b766ac02b" - - "0xb1532d97dbb8c85040e1ebc08649670b8980fd97cae2e23ab7f1b5af06c1f597" - deposit_root: "0x76d0915685d7293f2ad3c227cfd6208d198fc11d6eb154dca9ebf9f08b57a863" - deposit_count: 419 - execution_block_hash: "0x08a0fbfdd1c3183824d6f51a123e89b1a8dd1c208dea3a167bb8c47ff7697390" - execution_block_height: 420 -- deposit_data: - pubkey: "0xa174a13601df492b77a263ff54d9e30a0d9b248a1bfc559a4bbeb251fead9b72790f08bed89dbf224c1cb5548c44abab" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8a8d011198c4e426421f2563dbf5552ba950849f6b404a77de5f397f470ad349fc35c62df521f34d4ce8e5135f514df11654c7868da1b45d6d10193597cb0e1c33b8bff2f005c2eba783f1e7f5526cc85be30fda5d481ecc526b5a0a5733815d" - deposit_data_root: "0xb5100ee2bc58e4bcbeb35010d28c99f25f30db2dee32e8d8be5abfff81c6b980" - eth1_data: - deposit_root: "0x4fbb0a4745d73910a0e83ed47c036e561d5a974ba373106f4be04ee184f68009" - deposit_count: "420" - block_hash: "0x8644b9495ba9e4ea664a3d613b9aceb7e8a7efb507635cf2eea237ccfb3eeb5c" - block_height: 421 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xfb785265dae57039dc1b110d2edb8556f05c9f2013e0cd5b57982be1642c2375" - deposit_root: "0x4fbb0a4745d73910a0e83ed47c036e561d5a974ba373106f4be04ee184f68009" - deposit_count: 420 - execution_block_hash: "0x8644b9495ba9e4ea664a3d613b9aceb7e8a7efb507635cf2eea237ccfb3eeb5c" - execution_block_height: 421 -- deposit_data: - pubkey: "0x88a09d6756a015068efa04c8fca73dd96e2314e4f6e9d1aefdf19ef139eccb2000c3f382a18a3ade202641ed2c4b4267" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8bae90cb3fad0db9242b14e40a799f7ad163ca2a720864f4abdb6e28a352d19655d557420a4f4965108d247c82d3cf050224c04aecf9a351e57cfc008e27b60c991b02446d19ef1f3bb6f6bc932010d9bf6c54b4b09afd8131fd2d95fac89c11" - deposit_data_root: "0x87c39cef2326f57395a3143b07c46e7077d23cab48b40a25631ba38381e3f15a" - eth1_data: - deposit_root: "0x1bf71fb13806bd931b817a8be4dc90de192c8f2baafc12d99eb8ed922bcf53a3" - deposit_count: "421" - block_hash: "0xad379a83ea80b1aae55e118cdadfb133bbcedb14842e6a1a1fe4cdae049d0739" - block_height: 422 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xfb785265dae57039dc1b110d2edb8556f05c9f2013e0cd5b57982be1642c2375" - - "0x87c39cef2326f57395a3143b07c46e7077d23cab48b40a25631ba38381e3f15a" - deposit_root: "0x1bf71fb13806bd931b817a8be4dc90de192c8f2baafc12d99eb8ed922bcf53a3" - deposit_count: 421 - execution_block_hash: "0xad379a83ea80b1aae55e118cdadfb133bbcedb14842e6a1a1fe4cdae049d0739" - execution_block_height: 422 -- deposit_data: - pubkey: "0xb41b17006e2a6a6bce8055ee459557cf835fccaa851885903846dc190adc223f6cad199446ca225b6a65d40382344227" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8c1ba3a40657d6fc08753d00115125acec2821bfcdb6e272758086cf5eb38f6653924da5b296a24ec4099958edb6c42d1539bafe103f62c926d09f03424abb0cf189985dfbf042fee90cdc4efb1204eb251825a9b5771862686271c0744ea62f" - deposit_data_root: "0xdcfe0566760412e34ef921467df75a7ab63ba33709d01ec51509c229ea7f1f8b" - eth1_data: - deposit_root: "0x7ea9a20183569c27fa282cf8f41035a826ad1a4055bc201b03bfffd70a4ebf7d" - deposit_count: "422" - block_hash: "0x623deab0585c9ba3256328022621d3ea83974a0e95bd98cb4974fab7ff84c458" - block_height: 423 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xfb785265dae57039dc1b110d2edb8556f05c9f2013e0cd5b57982be1642c2375" - - "0xaab1605a8179a12b1e4e07684af74b5398445a216df5219be25801f44d75d56e" - deposit_root: "0x7ea9a20183569c27fa282cf8f41035a826ad1a4055bc201b03bfffd70a4ebf7d" - deposit_count: 422 - execution_block_hash: "0x623deab0585c9ba3256328022621d3ea83974a0e95bd98cb4974fab7ff84c458" - execution_block_height: 423 -- deposit_data: - pubkey: "0xaf9a969a70272ac2871fd689f13c0b478e27bdc5127f02c41efabd5814a902dc1e3ed8b3ca1786aafb8f112aa3c87db7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4b4f5e4b3bab060290c43259af77f83aab46b0d5b47cf7ec63e252bc1a1cbf4473bc03b6e0d70cd822d80b93a5d686109087af68ec598e991234c5692ca91844404d7be411ed10fe57100f9d29a89fbca2b3869ab58177079fb916d7a488573" - deposit_data_root: "0xed7664e9d1230b810ae66e70f7216a6cfc8dd8dfffb6781b25a724317713c991" - eth1_data: - deposit_root: "0xd4d4345b1ff711661fd8c50cb40512e1535b1e50b1abc36db5ca5ddc1d998963" - deposit_count: "423" - block_hash: "0x76d7667e463891a1f70c269d1a0113126715969b01f74733050cf0fed12f1f41" - block_height: 424 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xfb785265dae57039dc1b110d2edb8556f05c9f2013e0cd5b57982be1642c2375" - - "0xaab1605a8179a12b1e4e07684af74b5398445a216df5219be25801f44d75d56e" - - "0xed7664e9d1230b810ae66e70f7216a6cfc8dd8dfffb6781b25a724317713c991" - deposit_root: "0xd4d4345b1ff711661fd8c50cb40512e1535b1e50b1abc36db5ca5ddc1d998963" - deposit_count: 423 - execution_block_hash: "0x76d7667e463891a1f70c269d1a0113126715969b01f74733050cf0fed12f1f41" - execution_block_height: 424 -- deposit_data: - pubkey: "0x8faef0de1c486a7638c2a50af8f34e42b7f11bc1168c03678408702f8d11637f1faf893955a39897d797c851d1e6267e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa03d1ab7e650e5b593fc076f109653b6ab3bcbd4ab214a638c91c182073d90e44dadd8243ec6b6055a08d7876d67231d11be579189b12e04ae0f6aa2071fbfa25680102c2cd9612955bd5ccd11e6924acc1aca90f17939eb50e364484707d58a" - deposit_data_root: "0x618c9f801c71c3abb9c4a6b58ecd7bb0ee58537066ab15c04b86466d53acc316" - eth1_data: - deposit_root: "0x0ff9c5f8f3fce635e0c513c911e919396b682a34471d7a6d95f709fdd15c8713" - deposit_count: "424" - block_hash: "0x987c1a8ce6e27f94a0bac01816f3f4f54c057350d36bee07c00d83a62555e92b" - block_height: 425 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - deposit_root: "0x0ff9c5f8f3fce635e0c513c911e919396b682a34471d7a6d95f709fdd15c8713" - deposit_count: 424 - execution_block_hash: "0x987c1a8ce6e27f94a0bac01816f3f4f54c057350d36bee07c00d83a62555e92b" - execution_block_height: 425 -- deposit_data: - pubkey: "0x95bff09876e40fee59003b08bab452d9bd2dfae2c6411e153c4fc531f4aa26af8966049cf303865472d56643e3a3ad70" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa74bde4e1b2c55840198a66fd62671eee5e81c69c0d8418eb07c16c2003ba3eef1b8a7454b4fe1df5108e22bd8710288053fc45216787688320c3bebda2fb6e0402035ec9aa54f3f23e27c541e602f7fbbd4a259f5e359592486d027da49df31" - deposit_data_root: "0x370e5d74a292574246d28b1c7e721565b32b960f041768ff4727d395b42de7d7" - eth1_data: - deposit_root: "0x1e3367c89e570fe259bed5fe2dcaaf6cdf1130c88b0cbdbe3880cb020a578772" - deposit_count: "425" - block_hash: "0xaad57c3a976f49af3a86b03765169a35d6d785bb020ff2cf5ce6e6725d4e5669" - block_height: 426 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - - "0x370e5d74a292574246d28b1c7e721565b32b960f041768ff4727d395b42de7d7" - deposit_root: "0x1e3367c89e570fe259bed5fe2dcaaf6cdf1130c88b0cbdbe3880cb020a578772" - deposit_count: 425 - execution_block_hash: "0xaad57c3a976f49af3a86b03765169a35d6d785bb020ff2cf5ce6e6725d4e5669" - execution_block_height: 426 -- deposit_data: - pubkey: "0xa186ad3532db69f509d3f38223e388c9300031630ae02c5e13e6b55db6094554647fe52dbad51f931678c43d97f6b4c6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9464130c7ad45c27345e3383ba1e3a94ef7e5691fd9abd97b0cbd7c253a744ed2e953c561aa7196d2326a75c7a2410c4115b0968abf336ca7fa9414ab9580aa12b42e5bf991863df7ac526b8b00624704f025e6f8cdbdfb21411492fc0f30b38" - deposit_data_root: "0x7d7cc85629154e45c92dcc1032aea2cb10f384ef24c4d8903cf3f6f7f4140a11" - eth1_data: - deposit_root: "0x2b3611ca950982c5bef154794766299407c48416ca3e3326072e108f83081f01" - deposit_count: "426" - block_hash: "0x34b26f56bc1b5b8b92acee8b5514178a4e1262479ba3c67294330a2d9dab14c8" - block_height: 427 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - - "0x7f0c9697e17536d1ed08f791074bb7a4acf1a34de4a9d0e99d8e5057c1cd13f3" - deposit_root: "0x2b3611ca950982c5bef154794766299407c48416ca3e3326072e108f83081f01" - deposit_count: 426 - execution_block_hash: "0x34b26f56bc1b5b8b92acee8b5514178a4e1262479ba3c67294330a2d9dab14c8" - execution_block_height: 427 -- deposit_data: - pubkey: "0x976ecc14c14c93f8d0bcd6ee0a6f829de29fc13e167204e83d9c13d69065994af9c38424443114913e3142ca96138094" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x891aaeebcf8b8e5fbf12ae568c2782ac6ef3d42856d9476d12e7638bebd5f669bbaae802a46d7b9a41ea456547434ed806b583f696903bcc2a032961e7f123b4c0dc71356edb0db37de478452407fb48984e3d174fc821f707de94a67ef81100" - deposit_data_root: "0x3e4beb332aee13ce0e48353814b5368e9571a2467f070677265eb9b41c1514d3" - eth1_data: - deposit_root: "0x3b62cf4ee50ca126c3da8052bae1db595a921d5aa400aa5a4e7b14750e8aaa5b" - deposit_count: "427" - block_hash: "0xfee92ed291741bf5c0e55f66775aa3b61a501b735d0b4b1c633dc48a1da857d5" - block_height: 428 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - - "0x7f0c9697e17536d1ed08f791074bb7a4acf1a34de4a9d0e99d8e5057c1cd13f3" - - "0x3e4beb332aee13ce0e48353814b5368e9571a2467f070677265eb9b41c1514d3" - deposit_root: "0x3b62cf4ee50ca126c3da8052bae1db595a921d5aa400aa5a4e7b14750e8aaa5b" - deposit_count: 427 - execution_block_hash: "0xfee92ed291741bf5c0e55f66775aa3b61a501b735d0b4b1c633dc48a1da857d5" - execution_block_height: 428 -- deposit_data: - pubkey: "0x847ec3a8b963b0a40506ad2e2a2f9119456d36150eeb64856f12c85a85e12d59b295d953d845246be5aeccfb70c1dc04" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb9cb0dfb5fcb39c230277f2574438a7c19d54a4ec2e3e2ffe7129f492ddca723f92f30e73dcd08649dbd534ca2cf2adf1002df9dadd275afc05f4043b5ff7dbe6e85db48ccc1b2939652c7c2dd67553d40935ec4c8f6af52125096afaea8bdaf" - deposit_data_root: "0x7246ac04b767468d6e6962003af7f3c181600a6061e568046b7d4674a8cb6ba9" - eth1_data: - deposit_root: "0xd644b836a3cddf49cc1de8df9b2147a89e2cfcfe1eb18ea7d5af3415d20c5adc" - deposit_count: "428" - block_hash: "0x50f3f0653ab23862d6539b59676e3f7b9d7af24750232050ac04473e7d14c2ca" - block_height: 429 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - - "0xabd7629e265df3c45f91829f510911e1a6dc607cf1967e4cf7a6a1978741a35e" - deposit_root: "0xd644b836a3cddf49cc1de8df9b2147a89e2cfcfe1eb18ea7d5af3415d20c5adc" - deposit_count: 428 - execution_block_hash: "0x50f3f0653ab23862d6539b59676e3f7b9d7af24750232050ac04473e7d14c2ca" - execution_block_height: 429 -- deposit_data: - pubkey: "0xa6be6fc30a561b92e84333e413ce5be1d83de64ff5cef73611840fde672bf26763901912b37dfd8f1af85c3af87c8718" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x97b2556b101e1c5b723317713a0bc7ec78848d693b16475f1d0ef7679a75189510913cd7e211a9347242dfaae7ebd71e05010b840b2920cc1a36b4402ddbbab8d299b67cf49a171c8a39b30c75880a455f2055717a5021e390f1073d398825f4" - deposit_data_root: "0x28bacf6966c371a93169d29ba14a8a7f29b578c65832e521502eec860369c813" - eth1_data: - deposit_root: "0xbfc44c7d1d7f6b4df91c5cf86fa7b578915638c69752caa00aac61cc88f2531d" - deposit_count: "429" - block_hash: "0xd6afed7b6d18bb3524ad6fb17e2c46acdbcfbb3197f82e2b287d17e945a2b8cd" - block_height: 430 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - - "0xabd7629e265df3c45f91829f510911e1a6dc607cf1967e4cf7a6a1978741a35e" - - "0x28bacf6966c371a93169d29ba14a8a7f29b578c65832e521502eec860369c813" - deposit_root: "0xbfc44c7d1d7f6b4df91c5cf86fa7b578915638c69752caa00aac61cc88f2531d" - deposit_count: 429 - execution_block_hash: "0xd6afed7b6d18bb3524ad6fb17e2c46acdbcfbb3197f82e2b287d17e945a2b8cd" - execution_block_height: 430 -- deposit_data: - pubkey: "0xb1ea18e5c0a7424d06a9e77c65f2ff5047b5c4530e34e700c0d1bf15579e2afad228a8213c68eff8c14353d2e1f10053" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa965b7ec3ba5eb7d723ae622732f9398f875bffb7941b121c14daace185523a42e662151832132b8b296903c4432f77b02e06400a84268834eae712813f6bd4af66e571fd397c2dca316dba4c5ef7091ada36ddd5726d1db8e1e1f8ef6c40f6e" - deposit_data_root: "0x81e40c00c14deb34eb65e0a158104b60554fd0624bfa86b560cddcd29eb66750" - eth1_data: - deposit_root: "0x46bb5f072f410bcb56d1f87830a0db231802265dec6ac59acd451360db8f3eab" - deposit_count: "430" - block_hash: "0xefef471cb7829f27db63a66917e6141f86a58f90c076f54a2ea35302ac6bb5d3" - block_height: 431 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - - "0xabd7629e265df3c45f91829f510911e1a6dc607cf1967e4cf7a6a1978741a35e" - - "0x3bbb471bc80341245a119253a0bd27206485c8d671ce2ab1904d70b635c83297" - deposit_root: "0x46bb5f072f410bcb56d1f87830a0db231802265dec6ac59acd451360db8f3eab" - deposit_count: 430 - execution_block_hash: "0xefef471cb7829f27db63a66917e6141f86a58f90c076f54a2ea35302ac6bb5d3" - execution_block_height: 431 -- deposit_data: - pubkey: "0x8fff13510ac685f51ae914b7177bdf296ecd757385442ea0dbfc8841685b79b7d52c780a40e07723ea34d6179c6f3ee8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb501c7b840d94f74b23e82caa150fd57a830cc20a665a84267e069fb8a93ccc56e7faae237f875e910a533a8eaa20cce06783be38427523d18ceb88bb4fef649c256ea05bd54cde8e05bf82826857e023afdfad82da136a1586ff0cd23435430" - deposit_data_root: "0xabffe715ca708ebf5bebea360ea9c27f48e040dccc482bce560d84cf0c0f364f" - eth1_data: - deposit_root: "0x19e7f6f59f1a6bfc99344743800d79127bfa93e93118783888f7fd6bc3d23087" - deposit_count: "431" - block_hash: "0xe61cdc39257cdd0b1d973ef37bde4c7ad35fd8b5bd245be06da6a869f3be5364" - block_height: 432 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0x60f09c9d72bd6a4e2cbb2f9f54623e280baddba85f8c51e5a07912c0782c2997" - - "0xabd7629e265df3c45f91829f510911e1a6dc607cf1967e4cf7a6a1978741a35e" - - "0x3bbb471bc80341245a119253a0bd27206485c8d671ce2ab1904d70b635c83297" - - "0xabffe715ca708ebf5bebea360ea9c27f48e040dccc482bce560d84cf0c0f364f" - deposit_root: "0x19e7f6f59f1a6bfc99344743800d79127bfa93e93118783888f7fd6bc3d23087" - deposit_count: 431 - execution_block_hash: "0xe61cdc39257cdd0b1d973ef37bde4c7ad35fd8b5bd245be06da6a869f3be5364" - execution_block_height: 432 -- deposit_data: - pubkey: "0xad2d0e0c00b5a16d50a4ae56a3c0cf2e1b2263e2dd53ddad54679cdbf338300e1003af5ecb73ac3dacb8636661e142d5" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x841c98171049ea19762d010391bf195c097c130667801133259262d06ab7464e26cf14bf0dac3c8d08890db416a3301519474ab35920fd9ea961ebe80ae7a076f609fcf8cadf99c3bd46aac91456c6f543783cd042525bad6fe94730755330da" - deposit_data_root: "0xa2a0f194432b58ec992b183815bfd4f21a1acdde93677175ec830dc8ada25092" - eth1_data: - deposit_root: "0x83784abcabf728772306f379a348e2329b6b03428e8120b6deb3ba1aefba2407" - deposit_count: "432" - block_hash: "0x98b16968453d04262b7c05ee1a2b09bcbcf8e29473c6d1a74bcaa3d03af6eccb" - block_height: 433 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - deposit_root: "0x83784abcabf728772306f379a348e2329b6b03428e8120b6deb3ba1aefba2407" - deposit_count: 432 - execution_block_hash: "0x98b16968453d04262b7c05ee1a2b09bcbcf8e29473c6d1a74bcaa3d03af6eccb" - execution_block_height: 433 -- deposit_data: - pubkey: "0xb854086bc1f668ad8ccc4ed3a7a52eab65210717c3f8b9e6ebc27c1cf27ccba0a4056311c69cfefe875fa45e2cd26e69" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa94b7daed00dd9fdacdb5cdc0635b588e5c3ab124438d7a1a59b068708e2fc3de327e35ce66800db13f100ca332db93219ff60525e13d868c66b5920d76c38c4264b9e68ada4586642ca4f02ec33438989c7f1d00cb9b02dd49dfc47d401d8f8" - deposit_data_root: "0xda7276d3cd6c2ea2db69550570f8721ecb14d9e122e35c2d6090c600ad286bd9" - eth1_data: - deposit_root: "0x25028b90b69a3ff62a7a3cb3e67cf58130aa8868027f3b41613d71e8156162c8" - deposit_count: "433" - block_hash: "0xa783b7745e047a84c6270ab3910a09b000044ee37bca6caa2446153767236733" - block_height: 434 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xda7276d3cd6c2ea2db69550570f8721ecb14d9e122e35c2d6090c600ad286bd9" - deposit_root: "0x25028b90b69a3ff62a7a3cb3e67cf58130aa8868027f3b41613d71e8156162c8" - deposit_count: 433 - execution_block_hash: "0xa783b7745e047a84c6270ab3910a09b000044ee37bca6caa2446153767236733" - execution_block_height: 434 -- deposit_data: - pubkey: "0x95000d5a88212033fcc30690e930c2128685b97f26a9e2c52b75e1fb562a1db1b9af071207823ef1927db3ca65ee00ef" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8444dfc139521e5236c5f5637cfa17c87b6fd06e5ca8d4e33e522f02cf9fee562903dcd42f173696c981e1113be5bee5102f84730a31c9e03b45b753178c65d87335bf82cd600384fc4d03151ca94880b0df31bad1ab4f09d760c604ca16487a" - deposit_data_root: "0x184c58ea01f5c0c47d5dec98d30cfd9edeb11060b1c931cb3d117fd5cd6d7c4a" - eth1_data: - deposit_root: "0xbd639adce24d58cfbc0f226d6a045f400506c30d3388b4e520816c2ad1a5776d" - deposit_count: "434" - block_hash: "0xa36bde80ad1b5c6fa8848e656ef5c9c95411042e1fff4ffc23b4bc2bcb7e6bee" - block_height: 435 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0x923555fa4c1bad92de2ae74472f84089b9f15bfcf562d84eb249313dc6bb6ae2" - deposit_root: "0xbd639adce24d58cfbc0f226d6a045f400506c30d3388b4e520816c2ad1a5776d" - deposit_count: 434 - execution_block_hash: "0xa36bde80ad1b5c6fa8848e656ef5c9c95411042e1fff4ffc23b4bc2bcb7e6bee" - execution_block_height: 435 -- deposit_data: - pubkey: "0x929ab4e52386b139d32da163a4e1be5760f1d97bcfdaaa146f8b0348c94896b33fdbc3083ff3a41063e1b969a1f84080" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x81b4e7bf1ea952ddeba8fdf3e4b81162e5f7481fa9f59a95604fb0b8f8e4edd17e114d264ce0a236ce2459880a68c11317ac8aa2faeca6f80eb2e5cb53f106dd992e687c7b86536de330630e0c7de7ae16ef04646f05e98340d689157b704f0c" - deposit_data_root: "0x27578550b83a805d25278e7004eb819236d723c8e1be90d61b65d3fc50314abc" - eth1_data: - deposit_root: "0x27d9b9761517e56e0c390781d9e7e967a7376a27a09380615594f756608ab48d" - deposit_count: "435" - block_hash: "0x51f5137c853e6e77ec221f4c6185e11aa1f03537aa9c94d91d4a58ec8287466d" - block_height: 436 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0x923555fa4c1bad92de2ae74472f84089b9f15bfcf562d84eb249313dc6bb6ae2" - - "0x27578550b83a805d25278e7004eb819236d723c8e1be90d61b65d3fc50314abc" - deposit_root: "0x27d9b9761517e56e0c390781d9e7e967a7376a27a09380615594f756608ab48d" - deposit_count: 435 - execution_block_hash: "0x51f5137c853e6e77ec221f4c6185e11aa1f03537aa9c94d91d4a58ec8287466d" - execution_block_height: 436 -- deposit_data: - pubkey: "0xb126e91bd72a3289a04fa01e108f3ab4b77f2d20a93edc1c70f8ceda1df286461e92f73b7d2f85874abb1454ae1ef535" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa7f2a03af6e7075f289910a5b4348a6b2ff4d2f02633a44faeaccb965e6c2eb77647bc1489e6a8e39746fe29cabfaa92065161485194301a25617f81b96b7fc420eba955e13b04df59214faee482a439c3a2dd7cf5e3c9ba31a0309150b7d545" - deposit_data_root: "0x5ed3598bdbce2f0bc4a26dd562b02fdc5c80ee35c013ee7c28610ae954b6b5e4" - eth1_data: - deposit_root: "0x6dff34424e745612aca561b1be1d10859787245ad98e2447b7bc9fa7bde0f008" - deposit_count: "436" - block_hash: "0x4820d0b8cf715941790bcc4e7ee618bbbace730c2bdba686471fac3746020dc1" - block_height: 437 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0x7aa625d524b4796a9c8870fee5a28d37b822d5a6c4a9959b084e764ffa123a94" - deposit_root: "0x6dff34424e745612aca561b1be1d10859787245ad98e2447b7bc9fa7bde0f008" - deposit_count: 436 - execution_block_hash: "0x4820d0b8cf715941790bcc4e7ee618bbbace730c2bdba686471fac3746020dc1" - execution_block_height: 437 -- deposit_data: - pubkey: "0x8fe3f04f04422bb2c76f5cfddf80a7a2b01bef7170af5f52c94e64ba1f8f523ff18d09af9235e37d10b3ac8ba3cc6ac4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x94c9697875b08a8f6dd31af84ba20b702f8ac2a3488b6ea694bc1a3afd582bc5d52dff92b0c9dfa4ab6ab7080ed434b6047e3743f1007c560cdb7482eb09147d49c79f3118dc1fac66e918d25e86226042ede65d1ed7a262b8c165b78df81e3e" - deposit_data_root: "0xb40085a98ecff79943839d4d2cdd7ab9bbb72f90e94daf08c50efd26eba09c72" - eth1_data: - deposit_root: "0xf52ca6c22904e0ebef5336e0f010308fd124d5caed6a6043da92e1cbdd7afc56" - deposit_count: "437" - block_hash: "0x7692a6ca9efd1b0796eb01fcdb2b08fbc1739837e6f24eeefadf8cdad2a8062f" - block_height: 438 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0x7aa625d524b4796a9c8870fee5a28d37b822d5a6c4a9959b084e764ffa123a94" - - "0xb40085a98ecff79943839d4d2cdd7ab9bbb72f90e94daf08c50efd26eba09c72" - deposit_root: "0xf52ca6c22904e0ebef5336e0f010308fd124d5caed6a6043da92e1cbdd7afc56" - deposit_count: 437 - execution_block_hash: "0x7692a6ca9efd1b0796eb01fcdb2b08fbc1739837e6f24eeefadf8cdad2a8062f" - execution_block_height: 438 -- deposit_data: - pubkey: "0xaaf6cc6cfc559ca26b82790935d4919ccc630438032b76874a40c25d77e4b87db2401cef8ad1871011569061ec7badcc" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa47e64f4d650537e9cb7d858267ee6862b97504ed46d11def724be28d05bd9c08e34420d5f06a1342536a47f120e8a540eb453b08c94fdb4c2deb3400f9cf87ce90a7cc1b14c8dea450111fb91f54fe4460e238588a6b4ab8778d48b99438a84" - deposit_data_root: "0x99e08ee836bfaf2e9ccdcbfb308c88826e060edae11e3248a155049d4eaa772d" - eth1_data: - deposit_root: "0xb54a726ffc39347df5841efcdcdd4936894b92463db82208a447cf0071dcc95f" - deposit_count: "438" - block_hash: "0x3f48bbc9894a9a76b6faf91af106f773ade4285ed0b2e82591ff87514e41720b" - block_height: 439 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0x7aa625d524b4796a9c8870fee5a28d37b822d5a6c4a9959b084e764ffa123a94" - - "0xb3aeed2eb2f5e99db75fa362a4e6c7dccfcff20b110983ffd36ad0aaa1783e50" - deposit_root: "0xb54a726ffc39347df5841efcdcdd4936894b92463db82208a447cf0071dcc95f" - deposit_count: 438 - execution_block_hash: "0x3f48bbc9894a9a76b6faf91af106f773ade4285ed0b2e82591ff87514e41720b" - execution_block_height: 439 -- deposit_data: - pubkey: "0x8533f1c4e38a4f72c3593ab28ccb163bfb429242a8c23e731478e57950f563b62101b06882f25c8133ea357fad66522c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x861858db0f3721f816791c002f9d8f09254d0ba5015fc2fc609783cff8a36efc267392c0525e4e0fb4bfa96d58626732091853af4c16b9fa309e5295331a32f84b2c3be61d2c2dfc34bd9e59117c54a8c46ee50f9291ba2793450fc960e14530" - deposit_data_root: "0xbbb5ada0d7f4efa62bf0ba927747f1af9d4cfcbd76eb084f24ace3c8b0167fe5" - eth1_data: - deposit_root: "0xf75c37a1a191b865e14c6dac867208ae436a48d454410ad13cc9410bd8e3bdc9" - deposit_count: "439" - block_hash: "0x00c49ac28a1512771b4a976e03f723442c209b14068daba22e931c5b7e357594" - block_height: 440 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0x7aa625d524b4796a9c8870fee5a28d37b822d5a6c4a9959b084e764ffa123a94" - - "0xb3aeed2eb2f5e99db75fa362a4e6c7dccfcff20b110983ffd36ad0aaa1783e50" - - "0xbbb5ada0d7f4efa62bf0ba927747f1af9d4cfcbd76eb084f24ace3c8b0167fe5" - deposit_root: "0xf75c37a1a191b865e14c6dac867208ae436a48d454410ad13cc9410bd8e3bdc9" - deposit_count: 439 - execution_block_hash: "0x00c49ac28a1512771b4a976e03f723442c209b14068daba22e931c5b7e357594" - execution_block_height: 440 -- deposit_data: - pubkey: "0xa20e3bcb6b38b66c2de7a7b3d1731cf430ebb94e38897d9ab9a9463bfe813ecf65f39297d2a3daa803f66bff80d6b653" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x880a58b82783d37fac7f207c9ad45e6c7fe7f98cf843bd52733e328a87ab81a4b585f598907d594b2abfc03913e0983b00cba405f0478ec3ff59954244f36467b5002354dc3f56001cf0c06fe12535dfd3813a03e561f27d0fe475153a7cc46e" - deposit_data_root: "0x05bb8954b6ae43cbaf05ef800efe85d1755df3df92888c1390ac7524d332f2f1" - eth1_data: - deposit_root: "0xee960dbb2cb5694f6bd15e8fbe5d67369629cfcb89920dd6e05dd8c95ed484df" - deposit_count: "440" - block_hash: "0xd597af80422338ab6dff2432fe3d82e43c817e4512bb29e120492a6dfe622bd2" - block_height: 441 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - deposit_root: "0xee960dbb2cb5694f6bd15e8fbe5d67369629cfcb89920dd6e05dd8c95ed484df" - deposit_count: 440 - execution_block_hash: "0xd597af80422338ab6dff2432fe3d82e43c817e4512bb29e120492a6dfe622bd2" - execution_block_height: 441 -- deposit_data: - pubkey: "0x8cd7d05a46ad2e98ea82c438f12dc9fad70fa0b8cff2a731847efedc0efadef9a3f4d9e12cf8e9469d157d106f12626d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa580ca98af67b935e7b896aa223bb2ae6fc23313da1d47595e244f5f9f6aac65b7b396a196be2722d6cf036f8c293de81736674e13303d5a6c8d8893ae1fc5b572edd9a84bb0feefe7ec293f6d9050f9687bf930ef42dffabb554094c24962b1" - deposit_data_root: "0x4c2f5d2c7521c2fa4bc78d77d66c44bc032f688fdeae2a216221fb20f49645ab" - eth1_data: - deposit_root: "0x03e6ba3922640836649ecbb7649e8b5361eed51edc0e24919c4a2f258350be34" - deposit_count: "441" - block_hash: "0xf4a32ac25eb43a2dee79b2620ab6d79fcecfe26f5b4d1743a6c6e47057f8f478" - block_height: 442 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - - "0x4c2f5d2c7521c2fa4bc78d77d66c44bc032f688fdeae2a216221fb20f49645ab" - deposit_root: "0x03e6ba3922640836649ecbb7649e8b5361eed51edc0e24919c4a2f258350be34" - deposit_count: 441 - execution_block_hash: "0xf4a32ac25eb43a2dee79b2620ab6d79fcecfe26f5b4d1743a6c6e47057f8f478" - execution_block_height: 442 -- deposit_data: - pubkey: "0xab5fec7f75687d50f35f4336b7ac78108834f98d9979f0327cd11b1557b6ad9dc119889003384b3940b10f0af7b8314e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb71c223a9819d28964a114bf849fb9f765087d4d32019a5fccc1062ea217a68ae8475708179e926d60f60df70a39973d09e6e387807f287dbf2d02db7a45be47f428330d2d2900a531b716ea53f90e9109c3aa061c1291b484eacb3f574372bb" - deposit_data_root: "0x1d605f33e04dd29303dafd86bd3dd88e3fa4fe9f8a690c596af384d1515e4d23" - eth1_data: - deposit_root: "0x3f9a271478e34f229dbde7880720bb764f94cc3008ad30fc40b822fa46551e7c" - deposit_count: "442" - block_hash: "0x1306dfc166e3c7fdfe93ce194349c64b49f57eb0ab103fc426be4cc1ef7256f1" - block_height: 443 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - - "0xacc05a3daa1af57565781fd828122e22e64a429030d2e8b20702611e6f49bd47" - deposit_root: "0x3f9a271478e34f229dbde7880720bb764f94cc3008ad30fc40b822fa46551e7c" - deposit_count: 442 - execution_block_hash: "0x1306dfc166e3c7fdfe93ce194349c64b49f57eb0ab103fc426be4cc1ef7256f1" - execution_block_height: 443 -- deposit_data: - pubkey: "0xaa93eab6f16b0bee0a2eaf3d548d354a716e1bfa4047ffae14af55bb49bb994310265e10c7ab47ffa33d1c3eceadbb4b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa4d489afd3c027948032978b0384fed21b6013e19108ebd8f2937e3b32854c91ea80dcf183670be34574a05634eb06b4109970b34b0e998f9d09615a7b8ec277c2d62b43daacc9d346733060dd7f08208164c57cc54dee78de08c4d42774e51b" - deposit_data_root: "0xc91ecd7959d5713b9f528f6fdfdf456416f0c19514640347fd465d24352e35d5" - eth1_data: - deposit_root: "0x7bc70f5b38014ee13c40977439be4afa105e103bf24aff9c687c1d52def0d90c" - deposit_count: "443" - block_hash: "0xb6fcd3780b78de4f093046724aba76445fff193f0aab33ee3e08099403f5d076" - block_height: 444 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - - "0xacc05a3daa1af57565781fd828122e22e64a429030d2e8b20702611e6f49bd47" - - "0xc91ecd7959d5713b9f528f6fdfdf456416f0c19514640347fd465d24352e35d5" - deposit_root: "0x7bc70f5b38014ee13c40977439be4afa105e103bf24aff9c687c1d52def0d90c" - deposit_count: 443 - execution_block_hash: "0xb6fcd3780b78de4f093046724aba76445fff193f0aab33ee3e08099403f5d076" - execution_block_height: 444 -- deposit_data: - pubkey: "0xae363ffe8d6c6858d3c32967150ef670a2262ab9f46b3f2e3e4ae8910092bad1e7208ff9ba3f28b151dfab12f3474a19" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x803d1670174da10aa0eae9a24ef57151ce4fe2eb9f8c3144761f90fe483fd38d06aea4b7e6879afd43c5e1e3dfa223230edb7385dcd41d0d4eff69f94690ceab02bf9d5c35ecda1e30cc929403f64b0a08e9de644cbba5efcc5814ffbfc51322" - deposit_data_root: "0x1ec544baffa893fc7cc179cccb186cae5d98da2920f837e49b6cd683abcb8fb6" - eth1_data: - deposit_root: "0xb81e9256ace4aee3308b567f7fb37713dff69385afb6b57a436070f318c2bd7b" - deposit_count: "444" - block_hash: "0x3c24b3a1983001debec5a13e73083d0bb99ab232cc3b26985f78dae529564581" - block_height: 445 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - - "0xa1b8c431a46186490644ac091a8a657e248fdf1dbabd59a9e6c69007dce25c3b" - deposit_root: "0xb81e9256ace4aee3308b567f7fb37713dff69385afb6b57a436070f318c2bd7b" - deposit_count: 444 - execution_block_hash: "0x3c24b3a1983001debec5a13e73083d0bb99ab232cc3b26985f78dae529564581" - execution_block_height: 445 -- deposit_data: - pubkey: "0x83ae23912c04f49176c912a5d728224c3bbfac588e2203e37c6ca7520b01c6225b2830b0d37c8eb641d9abb44a648e28" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b5366124d214e3f88667a97297b75e2feaf3f54e09047b6e1d805499c961c76685f4dd16c8d169cff4fc17c401747ac0b6685693e80ab3324e4c77fcde51afb210799489d32a8251d6c25623abd5eb4930ab097b4d6d6daa38ad752cff3b435" - deposit_data_root: "0xe76b1cfd301efe07f3af9878e54067b811f1872abd8cb7a02442fdf7cadd6d54" - eth1_data: - deposit_root: "0x99b58fdf354bd79892edced1978af6264777847c433d692efbfedff57526c7bd" - deposit_count: "445" - block_hash: "0x7480cf6cd450d4e3d9a461c222e8e0fd3a4416ac6c66a18d95bd870ed0294806" - block_height: 446 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - - "0xa1b8c431a46186490644ac091a8a657e248fdf1dbabd59a9e6c69007dce25c3b" - - "0xe76b1cfd301efe07f3af9878e54067b811f1872abd8cb7a02442fdf7cadd6d54" - deposit_root: "0x99b58fdf354bd79892edced1978af6264777847c433d692efbfedff57526c7bd" - deposit_count: 445 - execution_block_hash: "0x7480cf6cd450d4e3d9a461c222e8e0fd3a4416ac6c66a18d95bd870ed0294806" - execution_block_height: 446 -- deposit_data: - pubkey: "0x86d99bd43392d34879ff4409aaa44f1a42b34471a59136ff2fc2c5d0f3bee905108958f4a0de383597476b6f8fa380eb" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8560371d260fcc434e0da634b18e1216ca8d63ea40486020c9568fbd959ed350f8b8b3fc1f6f88a48b7517d927c08c04190dc945da272ece929599becb29c8ed939ccb31cc4f316bf2c7341e2373f44ff8515858606fab8165e4ac6356f037bc" - deposit_data_root: "0x28f2965716ad7616ab299ba34a56b5da43142e03867076aa477efc5774f7aa12" - eth1_data: - deposit_root: "0x440365613909570f9c374a476876624deed2636114a1dece1e7051d565d687f9" - deposit_count: "446" - block_hash: "0x452bf66254b503662848f580173b6b13a10efdf20d2016683b67c4d0beafbad4" - block_height: 447 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - - "0xa1b8c431a46186490644ac091a8a657e248fdf1dbabd59a9e6c69007dce25c3b" - - "0x22ee861ca07aca649f7d8b981e6c3bf4615ac3944a6acefbfbc843fe66b2d14f" - deposit_root: "0x440365613909570f9c374a476876624deed2636114a1dece1e7051d565d687f9" - deposit_count: 446 - execution_block_hash: "0x452bf66254b503662848f580173b6b13a10efdf20d2016683b67c4d0beafbad4" - execution_block_height: 447 -- deposit_data: - pubkey: "0xae47eed8ebfbd33bf1e92f52fc40d16f4b2b5e0cf4f54f2463af76f83cf0fef26475dff6c5b9d78f81925ca7d51190ed" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x80f785a5c57201068c993fde0c2efb2004cfe05a03a65bae1562b9663fd62aa6a27fc529c69966838b3d2e41014eacdc1211d67c7ecfafb32d92e34a1bfdbd4df20f3051b7398715b95f34046e1e157186f2502725000322f66b6684ac7a5679" - deposit_data_root: "0x4c4ee3b1458b7a43155652b0c8e734776ed957d7f0baadb41e9c69615240a8fb" - eth1_data: - deposit_root: "0x024dc727a35948ddc2d5c8def41df75d4ab5f9c638d8420fc130ce350d4ebf5c" - deposit_count: "447" - block_hash: "0x5d0fc230f957aec298b3a4f35bd4dbbdd2a71de4696668501310240d0c3c36ff" - block_height: 448 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0x2586924062b8d4cce4bda9de914e2a85d8352e0ea9d1512a816b099cbcb65c05" - - "0xbe065ffe4f04afdfe075ddd6d51b69a8b470abe91f51e18845e3dd959b272134" - - "0xb009f607491090689f616596852977e7f2617f96388353655e0b01bb9e3c5477" - - "0xa1b8c431a46186490644ac091a8a657e248fdf1dbabd59a9e6c69007dce25c3b" - - "0x22ee861ca07aca649f7d8b981e6c3bf4615ac3944a6acefbfbc843fe66b2d14f" - - "0x4c4ee3b1458b7a43155652b0c8e734776ed957d7f0baadb41e9c69615240a8fb" - deposit_root: "0x024dc727a35948ddc2d5c8def41df75d4ab5f9c638d8420fc130ce350d4ebf5c" - deposit_count: 447 - execution_block_hash: "0x5d0fc230f957aec298b3a4f35bd4dbbdd2a71de4696668501310240d0c3c36ff" - execution_block_height: 448 -- deposit_data: - pubkey: "0x9286bfc698dd8f807748245c4d13de02910fcd8bc9b375a308d8a2bc7c73a05c8254b73931e448fbb2cf2d24f8b42f56" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa4d210f5d05cc9475174d918b7f495c5a91291e08eb6e747a513265d3d7b52444df7213e1d476b8513f6ddcc90bf97c0e5ce888570f5537cc450b14ae716bc5bde3a8a251b9feb35129b63f52df8926494b921c0c2317548c680109503acff0" - deposit_data_root: "0xffe2744a1f6606c4f333a1163e422c905af1da3faa8355a7c1d14b4d96cf3a12" - eth1_data: - deposit_root: "0x783f4af9f9ff701317e0a1fe3c9772aba6748d2c3278d5ab1bd30feb212dbccf" - deposit_count: "448" - block_hash: "0x3ca4825aa555d4e316dcf61ea7d127988b176af164b1705bab9831427b3a48c8" - block_height: 449 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - deposit_root: "0x783f4af9f9ff701317e0a1fe3c9772aba6748d2c3278d5ab1bd30feb212dbccf" - deposit_count: 448 - execution_block_hash: "0x3ca4825aa555d4e316dcf61ea7d127988b176af164b1705bab9831427b3a48c8" - execution_block_height: 449 -- deposit_data: - pubkey: "0x98857d698710dbc26a57f167d463de3fc8e15fa4221d8ef6d8853c7843b04d2119a865215075f52674340300e1ff58a7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xad731a0a79a0d708d244ee8766bca38692a89fb89abca67ed6ed6bb672daf44ab77fa66ad298f0a158e72d3f7b3a55c719700c6173b8447d7cb46b07336fbae244fa124e3f98b825b7dc10c9429fc85dc4a5aa671006021aa0a72e5552b95ef3" - deposit_data_root: "0x6af628d2a153eb81599f51910fd685f52bd32fb10ac3556bb0c608b35afc0c5e" - eth1_data: - deposit_root: "0x03cfe15696d6d04ec988563cf653ae99e72a85641c1de5499e287c0dbfc3fe86" - deposit_count: "449" - block_hash: "0x090750899e4923ca431e67389a655fe6080af94162863e81712b21111ffd5587" - block_height: 450 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x6af628d2a153eb81599f51910fd685f52bd32fb10ac3556bb0c608b35afc0c5e" - deposit_root: "0x03cfe15696d6d04ec988563cf653ae99e72a85641c1de5499e287c0dbfc3fe86" - deposit_count: 449 - execution_block_hash: "0x090750899e4923ca431e67389a655fe6080af94162863e81712b21111ffd5587" - execution_block_height: 450 -- deposit_data: - pubkey: "0xabb8d751f2f97cd345688187accda8e00395503e500bd727b82b0be88484587776c225d64ca72703c186d6d0cb248e76" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83ea0cdfe5f2641f06f69411ac348b2429f34544f17fba693e58a7c395b86afe7cb52ec681ee2b92865b38a2ef600f2708eb9746987cd9a4bd831a2d74953e17077c8eff72002d1e6dcfc63693c42e6c48801ed6960480cb8e88b36bdd67df5a" - deposit_data_root: "0x966d2b44d3db4cd7fe7b890d54612f94f5e66aff6bb82f352e17ad42a35c7f60" - eth1_data: - deposit_root: "0x31cb5ea97e5cdb9ca3d10a4098e70b8fd784e6796603c63634eec24610665a1a" - deposit_count: "450" - block_hash: "0x1b42b2023d6aab58dcf21a8f96a4fd53a9980a99a92278d95bad77b79b3eb143" - block_height: 451 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x2ee848cde5b8b481d247e89b448dbd7e7fb077ce49a7a8777cb3d52d858452ef" - deposit_root: "0x31cb5ea97e5cdb9ca3d10a4098e70b8fd784e6796603c63634eec24610665a1a" - deposit_count: 450 - execution_block_hash: "0x1b42b2023d6aab58dcf21a8f96a4fd53a9980a99a92278d95bad77b79b3eb143" - execution_block_height: 451 -- deposit_data: - pubkey: "0xb88f1156883e6edf6df2f4f94079d373992e2d236ddf7003cd8cf84d73e371d16d4397fc20163eb4d8b5ff134beb4b51" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8fcd914835883541cef44b08c1b3fc7c91b28f6fb491b9beff48554ac227dcb6ed535bb1a6cf8cd9be97f495420f358116c39749895d17784a6828bbfacc22c6bf3c42d17f8e4a8900ea17be43e6c5b27f4a83793ff68b26a9d9fcebc6a376e8" - deposit_data_root: "0x88c1120bb5fa6ab67bf2b80803a27302619bdee14a10cf97df1e56ded1ed54d3" - eth1_data: - deposit_root: "0xe8b7f46dc7475278120b4752ae46cf7b4dc209ad1948739a5559bb8ad2c43846" - deposit_count: "451" - block_hash: "0x58a48bedd093c961425032a2b01bcc2789f04af84e1474c57d1739a0288553ce" - block_height: 452 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x2ee848cde5b8b481d247e89b448dbd7e7fb077ce49a7a8777cb3d52d858452ef" - - "0x88c1120bb5fa6ab67bf2b80803a27302619bdee14a10cf97df1e56ded1ed54d3" - deposit_root: "0xe8b7f46dc7475278120b4752ae46cf7b4dc209ad1948739a5559bb8ad2c43846" - deposit_count: 451 - execution_block_hash: "0x58a48bedd093c961425032a2b01bcc2789f04af84e1474c57d1739a0288553ce" - execution_block_height: 452 -- deposit_data: - pubkey: "0xb37a06d51bf2a06b938b2a1d84fc0e9a0d45e753401035d0c7557b01d200c302169a06c5276ed77b7b832a885376522e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb8152b87ca263de22dd75f0ef8197369bd750813ce33e8d49a67ee27ef560f384fe388f05a14aa0ac1a15cc43b7dcabe02bbd2035531e677009d17a552338e34e1bfdeedffecdc32982ed80ff2d863f39ee6b1e04136ea43a129edc137f60114" - deposit_data_root: "0x719a442d020d219e23f2c202c972311f1eab73fd8bfe61a3518405c83c4df232" - eth1_data: - deposit_root: "0xb522e60c52c239b043e08c2f0cda07c615337f6067f9f76e794b1f71145727f4" - deposit_count: "452" - block_hash: "0xc276347716f8b77e58ce6840bae2e5fa0167bcc32590b414298060c7c70bc921" - block_height: 453 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x6cd34b4abd5cf459af14ab7b1b53f14c3f3c752241056a9b64349432d18e4f4e" - deposit_root: "0xb522e60c52c239b043e08c2f0cda07c615337f6067f9f76e794b1f71145727f4" - deposit_count: 452 - execution_block_hash: "0xc276347716f8b77e58ce6840bae2e5fa0167bcc32590b414298060c7c70bc921" - execution_block_height: 453 -- deposit_data: - pubkey: "0xb960a785f7ea8fc1d9886ff43e3b736cf7fab383087b6f1d85727473d909a91215ec515fe72783d703e8d214cc72dbde" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa429b2d68b1a5e8493e942954a03d4cf7234280c5c48ece941bfaeb877351da8c9fc915141afb1e9f8ebca3eb4c1a7f006f4ba60b43df7951a87f9dcb7286b8bdcb8e34e3b1e73b53645425e35d68a7b95688b3557bcb30f7c45de075d3620d" - deposit_data_root: "0x80b0dadc1f658351f89567fc40f0775d9a496f4e72380fc1187690de3223a11b" - eth1_data: - deposit_root: "0xc9f3220a5eef0065415e6ff59ca524424b32ad98e61d6a5fa58e3addf0c1d3b1" - deposit_count: "453" - block_hash: "0x99799c65563071761573abee510fc22423fd463f2f1ffc6c1185038e82171f51" - block_height: 454 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x6cd34b4abd5cf459af14ab7b1b53f14c3f3c752241056a9b64349432d18e4f4e" - - "0x80b0dadc1f658351f89567fc40f0775d9a496f4e72380fc1187690de3223a11b" - deposit_root: "0xc9f3220a5eef0065415e6ff59ca524424b32ad98e61d6a5fa58e3addf0c1d3b1" - deposit_count: 453 - execution_block_hash: "0x99799c65563071761573abee510fc22423fd463f2f1ffc6c1185038e82171f51" - execution_block_height: 454 -- deposit_data: - pubkey: "0xac748de8d5418285e99661d22ef888ee9b19fee4e3bb2db1fbd4404ac32fabf9ba2d41450c2f2ba42bd97cdf28a349b8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x89cb911ffbffec15dfaec2c21298681348e4c02cf3e6dde9c7fc73093b94115cf433a485dea3561efcf7feb2cf9c2ede105004f477bad9be075e408dd73ef9c3be78862c8b6b3bf4122b28f887ae0a0120bc7a754e1f46dcc6b4b9606ddbc23b" - deposit_data_root: "0xcd7377eac780c66d2dd90fbcda1563fbcc7f4cb53c0d2e2f4c22f88005755562" - eth1_data: - deposit_root: "0x20d947edb9a78bcf6b5a8ab78f2fc9d1b08a451d553977ae010d2cdc39566e59" - deposit_count: "454" - block_hash: "0x60bc4577621cbd0cbeb9238733a408f77bc2a1958775e864f8dbaf88e09be44c" - block_height: 455 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x6cd34b4abd5cf459af14ab7b1b53f14c3f3c752241056a9b64349432d18e4f4e" - - "0xfa8b871b3d268957a0da1ff487492cb4e8ac619d397f80e1a36987ca54ef56f9" - deposit_root: "0x20d947edb9a78bcf6b5a8ab78f2fc9d1b08a451d553977ae010d2cdc39566e59" - deposit_count: 454 - execution_block_hash: "0x60bc4577621cbd0cbeb9238733a408f77bc2a1958775e864f8dbaf88e09be44c" - execution_block_height: 455 -- deposit_data: - pubkey: "0xa89895024ffe3c247d5515a8f2cf3e5275dec9c1c3143d7a4dbabe1aa16761ca69225bd2921a4afee83e01cb4c83c25f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x84d1d54c7b7afe591fd9be6342e92f4a1e0a0983a73cbf620c9653f602df0b9f1fd205e2b5e21edb3529cfb4fb200681012beaf6de044ee3198b256134c1a2b655df3fdeba942999f6f4e1642fa34b6185c5016d353e19441fcf55cebf6fd085" - deposit_data_root: "0xd5166ba088a056939b9bd3f7fc516190e2dd84b2f2872c225236a97908cf5d60" - eth1_data: - deposit_root: "0xb9093524f50637874422a739e135b55224a74123bf8d04b993a02921a5401dae" - deposit_count: "455" - block_hash: "0x6a4fb53350211cabe2bc406b3da41c4fc959922aa4953a42326fa0c58b5db8b8" - block_height: 456 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x6cd34b4abd5cf459af14ab7b1b53f14c3f3c752241056a9b64349432d18e4f4e" - - "0xfa8b871b3d268957a0da1ff487492cb4e8ac619d397f80e1a36987ca54ef56f9" - - "0xd5166ba088a056939b9bd3f7fc516190e2dd84b2f2872c225236a97908cf5d60" - deposit_root: "0xb9093524f50637874422a739e135b55224a74123bf8d04b993a02921a5401dae" - deposit_count: 455 - execution_block_hash: "0x6a4fb53350211cabe2bc406b3da41c4fc959922aa4953a42326fa0c58b5db8b8" - execution_block_height: 456 -- deposit_data: - pubkey: "0x8e0cd6b49327ba93279e394f62cb84375fc63bbac7c72eb4f90cd5d71e4cf34f1fe0d92b82ce51e60d9255a0fe99e021" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb16f03ea11ebd8f5f5c864bf87df767429229be5fad7b41bf26fa025bc4248af339ff07be292f5661d975344c4947d871368db8fd3ab8995c7ba358e4e73218b169fa32fbcc4839f9d1a1a9ef52b4169929d48d400783cc959ad4ca27a3432ad" - deposit_data_root: "0x35d372da653d0c041ede27965e52876b47afacc14fc6ed6eaf37580bd5f85177" - eth1_data: - deposit_root: "0x88685588119ba1394dfd58c01b558e18fd9e1630431136f478f0099a37fad6f4" - deposit_count: "456" - block_hash: "0x16b9630f3d0113ff5a3688bd3ecff00d4958a1136f6e3e9c6155a35288c51cd6" - block_height: 457 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - deposit_root: "0x88685588119ba1394dfd58c01b558e18fd9e1630431136f478f0099a37fad6f4" - deposit_count: 456 - execution_block_hash: "0x16b9630f3d0113ff5a3688bd3ecff00d4958a1136f6e3e9c6155a35288c51cd6" - execution_block_height: 457 -- deposit_data: - pubkey: "0xab195eaafc5a60c4c91722f53e19fce9c04ba9655f433a18f0472606ec826fbf77828d16f859297a9272d2b4fe50403b" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9297d77b34934381a63a39e25ae8ffd26a67bb1baaf6ce2d39ae53dde9e0ba2eb9ea5f6cd61d9f82a73e3c8f00220b5c14ded82f49ccc953a82a6f352a693d57ec256877122b2caf56c8405269a44ca255de858a1af5504388c4a04b71bdde1d" - deposit_data_root: "0x69c7a9d2550955ae43c7a6790737017c37d9d2c353af01686d4736259ab45a77" - eth1_data: - deposit_root: "0xcff79088a43be898c147090a135293d6ac0b9a05370a36aa6403897e449b6d74" - deposit_count: "457" - block_hash: "0x66e0fcb1ac380fb70e9a3bf4771276b0a1bd23bf7cf6eb37fb3b919c7da07e98" - block_height: 458 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - - "0x69c7a9d2550955ae43c7a6790737017c37d9d2c353af01686d4736259ab45a77" - deposit_root: "0xcff79088a43be898c147090a135293d6ac0b9a05370a36aa6403897e449b6d74" - deposit_count: 457 - execution_block_hash: "0x66e0fcb1ac380fb70e9a3bf4771276b0a1bd23bf7cf6eb37fb3b919c7da07e98" - execution_block_height: 458 -- deposit_data: - pubkey: "0xaea50380eaca9b09e5baec21e5f522c964b0ac8d4aebb8e1a1a196a01fb5c2c87f524be56a57d2f80274d1a0d2ed7fa6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x954643a0a8494712b0e1c45e089b17e84bfe57e4d901841e2e2381b303b724caf34741564ba450c8ef8469999f22a9a811c4b14c4599e2c3caa45f306264b7149402c643e4c82fafa3c966cd0bef954a214c06311ccf6a812b1ef43ea279a2b2" - deposit_data_root: "0xb131ceaf7a69f35f76b5c8e6ccd346ccb2f10cf14a173d12758dd4a9fed3c0d7" - eth1_data: - deposit_root: "0x1ef3716bf7e7a51f7fc622319f07a89a2ea1c2bd4a6149e5ea6e069e63a5dc66" - deposit_count: "458" - block_hash: "0xbca1858a0a0f800b2b1921341c915c917e67ba447b530834ddab39c1b4b5dd59" - block_height: 459 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - - "0x2f22c52f2268d8873a91866c36b0c38a75a64c6598e974ce14cf6b0b783fcf8a" - deposit_root: "0x1ef3716bf7e7a51f7fc622319f07a89a2ea1c2bd4a6149e5ea6e069e63a5dc66" - deposit_count: 458 - execution_block_hash: "0xbca1858a0a0f800b2b1921341c915c917e67ba447b530834ddab39c1b4b5dd59" - execution_block_height: 459 -- deposit_data: - pubkey: "0xb68a35785d29094de3457406b8f974581ab72e2aea2c3ec1ffd21b0326297f09a838b669e3e1409e127a3bc3eaa8cb72" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xac9f75a49603af9cd321f521460eda36c3a1dd7eb88bff394e7692b8d4dc7e7c33868025c7cab4b365693a8743d514d5029c238b47357486bb3b4cf31f87c9ac07327ec39858fc89b580dee5ddcdf7d089acfb238cec502403de5bc2487ddb6f" - deposit_data_root: "0xda57dcc5780f0fe914a0f88d1db9b36324b9e13a0fc44ad777ebec7a8ac154a4" - eth1_data: - deposit_root: "0x6262414f9280a354022ae8b80692ab01589f0f022775dbc08e7cbaabe1feb343" - deposit_count: "459" - block_hash: "0x88421a8eac3dd6665ad9040506148cbb90c37ff8278e3286d36eb41939177435" - block_height: 460 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - - "0x2f22c52f2268d8873a91866c36b0c38a75a64c6598e974ce14cf6b0b783fcf8a" - - "0xda57dcc5780f0fe914a0f88d1db9b36324b9e13a0fc44ad777ebec7a8ac154a4" - deposit_root: "0x6262414f9280a354022ae8b80692ab01589f0f022775dbc08e7cbaabe1feb343" - deposit_count: 459 - execution_block_hash: "0x88421a8eac3dd6665ad9040506148cbb90c37ff8278e3286d36eb41939177435" - execution_block_height: 460 -- deposit_data: - pubkey: "0xb90a59e63d66d20d5e9a3b45be51fa7963da5ee137a126740036f432c50eecaade78c4a5bc92fe1908a153b15ad92a25" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8397c642292c99d42f208b26b2dd82f1e8d214dbb6829b70cf5a55c710a37572fb77e02beafb4c2208e7c6a965529009021b0d55dbf63c6eda839187158f5cb2141f25464da3802e2f411ec7bf5671225b231d2a4a48ed90c72ec8bfbabe30d9" - deposit_data_root: "0x51c105f98c9892d6d1cead0485fe9058e733fbff334a4442e409140899cf445f" - eth1_data: - deposit_root: "0x34fd2a662b2220a66fc9b966be349b6c80781bf702a8702957ebd775a35e2e6c" - deposit_count: "460" - block_hash: "0x07fd13866775681d13fc3a2f7fd2ec38c86341a724a12140ed79d6c95bf1ad8b" - block_height: 461 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - - "0x689f0a0b6d57f49f4654c506592ef965bcdab3336c358f30213e2d5d3630415a" - deposit_root: "0x34fd2a662b2220a66fc9b966be349b6c80781bf702a8702957ebd775a35e2e6c" - deposit_count: 460 - execution_block_hash: "0x07fd13866775681d13fc3a2f7fd2ec38c86341a724a12140ed79d6c95bf1ad8b" - execution_block_height: 461 -- deposit_data: - pubkey: "0x9826a9986a35754dfdc31e69bb8b073eb7293aaa747b0990da43c2c04bc001c78257ab3a1a91d3c7f2bb9c8395ed9424" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa569d9ac3ef0d59e22d7776348817afbbe1c4213c2d827dd2c75cb4cc7992fafd838e5661b98a8f77712a82eb3a6e798106b7d3ff6b1376de96bebef0a915e648d08fee8478c5e39e8085693ad0af6ab4e5dfb0f22136e9d2f1a7d1b1a6d6482" - deposit_data_root: "0xb50cfbee3651658b71f15612069c1ade31ca2a26449985ef20b99f3ffbd78353" - eth1_data: - deposit_root: "0x76934fb0b2c000d79600b534433d9ae771971013b7e6ff1b5459ff86f9fe09c7" - deposit_count: "461" - block_hash: "0x9ae6b3c353ce3cb09bfe114d6b4f3a02e3119baff9cd693a7215a3036299c7ea" - block_height: 462 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - - "0x689f0a0b6d57f49f4654c506592ef965bcdab3336c358f30213e2d5d3630415a" - - "0xb50cfbee3651658b71f15612069c1ade31ca2a26449985ef20b99f3ffbd78353" - deposit_root: "0x76934fb0b2c000d79600b534433d9ae771971013b7e6ff1b5459ff86f9fe09c7" - deposit_count: 461 - execution_block_hash: "0x9ae6b3c353ce3cb09bfe114d6b4f3a02e3119baff9cd693a7215a3036299c7ea" - execution_block_height: 462 -- deposit_data: - pubkey: "0x854de57710b835c0253eef8a20d2e6f0ef8d260eb4443bbd5c38de0acd38035f0c1ad5d09994744588a497f8eb3b747e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa818e6ca068133ba1cb41c57f6b6b16db4ff64bdbe67d8207206989d8efc0021fb002976c3cff587687741db6d1ab6a200730acfe51121f49792c11865efde86e8d7ae4750f047eb18d544fcbe0aa03b73a38210a914cc1eb82012cfc9e0bb22" - deposit_data_root: "0x48c0a3d3ca1ae0f20cd20838fac325cdc0b92253a3431bb9033a2b4144afb08f" - eth1_data: - deposit_root: "0xe74c79921d452c89e27a9414c76a65286982c0951d4ee0eb486361fc8208e5e2" - deposit_count: "462" - block_hash: "0xa11e6044fdd05aa46ecf17d49edb46d91545f750db221a4edbab2663880d2cee" - block_height: 463 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - - "0x689f0a0b6d57f49f4654c506592ef965bcdab3336c358f30213e2d5d3630415a" - - "0xc47891c2dae75dac43cad9c632bfbed1549eab22922ad0829e49fb45e3bfaa98" - deposit_root: "0xe74c79921d452c89e27a9414c76a65286982c0951d4ee0eb486361fc8208e5e2" - deposit_count: 462 - execution_block_hash: "0xa11e6044fdd05aa46ecf17d49edb46d91545f750db221a4edbab2663880d2cee" - execution_block_height: 463 -- deposit_data: - pubkey: "0xadc1109ded58e828f3c992b3d367b8e8d9d5cbe102fb171b4f4dfa66a181ab040f13d15a38b10e50c8c87432904b6e1c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb8799635089cb0c7b9703daf090358b53459304538f784eea9802a284bed2adb69c3c2e3b244da77b7ec5b420fbe2a4e1220c59059d904847b4750f7c188c92cc826ecdf1c6650f789856dcb49c7fbf380c76702169dfecd0c7d076ffecdfa1b" - deposit_data_root: "0x8e7112b4f311f4c3a3a295185ba65739fc5f0a7bc06e2a561fd9f5ec1e3c56dd" - eth1_data: - deposit_root: "0x8dfaa386c19f0aa3bb523cdf7edae9a99a54ba5af875225e8ef106d89a9a7583" - deposit_count: "463" - block_hash: "0x66f1fe4d2e95c883b0a7698c48ad71aaaed1206a9ae8cce0d46c99a2d1834076" - block_height: 464 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x53c1bf574da722d22635feb98ceae3cfa185fec248396946d571681fb60e9005" - - "0x689f0a0b6d57f49f4654c506592ef965bcdab3336c358f30213e2d5d3630415a" - - "0xc47891c2dae75dac43cad9c632bfbed1549eab22922ad0829e49fb45e3bfaa98" - - "0x8e7112b4f311f4c3a3a295185ba65739fc5f0a7bc06e2a561fd9f5ec1e3c56dd" - deposit_root: "0x8dfaa386c19f0aa3bb523cdf7edae9a99a54ba5af875225e8ef106d89a9a7583" - deposit_count: 463 - execution_block_hash: "0x66f1fe4d2e95c883b0a7698c48ad71aaaed1206a9ae8cce0d46c99a2d1834076" - execution_block_height: 464 -- deposit_data: - pubkey: "0x87068fec51465d586d65ff531b32fe20d2e02535c08883721654338878b1aee8479fab486a19de804da1f301443f69d0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8e8b77fbdab9f6a210e3d8dac73df968fec07362e42dbddfc5533b103715438897012d2d49b64ecb920ad8eee641cffe06706a2af60897c44a21bc6c6581e878235070c197d57f9f8882bccc88feeafb1d0203569be51063663f82027a3da08c" - deposit_data_root: "0x14cab7fbbcb0c52a1c60cb65d05f7be70e33e4d4e6367ddc931482e6d839e76d" - eth1_data: - deposit_root: "0x42a835e727b7aaf06d891a55d8b7a067512cc87046e043dada051674d3d61438" - deposit_count: "464" - block_hash: "0xbdb7f8ea7927498e58540bc2b1d70b082c025dbe0d41fea325e7e65427d396dd" - block_height: 465 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - deposit_root: "0x42a835e727b7aaf06d891a55d8b7a067512cc87046e043dada051674d3d61438" - deposit_count: 464 - execution_block_hash: "0xbdb7f8ea7927498e58540bc2b1d70b082c025dbe0d41fea325e7e65427d396dd" - execution_block_height: 465 -- deposit_data: - pubkey: "0x95322a22f4abe8eb0a0f4cf8876c31abeb3684ed984f6b595b9f3b9527b60c5f730d03d4960f5094210a3f6383691348" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x921dc1028ad56faf90a0732ed439ca36f5f4fa643bcad13b95309a6afcab2ef45f535601289b18d3b68244a23b598e6a014d3b75ec07afa43a2718b9c1a670d8587d3fd8e6254204f6d2c3937ca09cd0c80de526b8bbe2150f4d3e05c218a747" - deposit_data_root: "0xea4b7f1637589d380ebdd0b75914919c07ef2936943a96865acd55028cd7c065" - eth1_data: - deposit_root: "0xf289bbf81113da5f485fc53db8a774b6817e4648e8c1f5e7136c68ffe1688fb6" - deposit_count: "465" - block_hash: "0xf02d6940bc1291413166041a1c800d2d86203d07151c71ed39604d78e50800a1" - block_height: 466 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0xea4b7f1637589d380ebdd0b75914919c07ef2936943a96865acd55028cd7c065" - deposit_root: "0xf289bbf81113da5f485fc53db8a774b6817e4648e8c1f5e7136c68ffe1688fb6" - deposit_count: 465 - execution_block_hash: "0xf02d6940bc1291413166041a1c800d2d86203d07151c71ed39604d78e50800a1" - execution_block_height: 466 -- deposit_data: - pubkey: "0xb43fcdad30bb652a82ce6033d6cdd6b004f58eb102d54f4de5a59c17c774c66637e7ed8078b19afe55ed34ac2b969fc7" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83444116edd61f30eb0ad94e86f36834a2b99db16ff37a19139349456b8afd64b7dd6c92e7fa8f6fe9bf13de87c39fa80d3dafdcf00f9285e2610a63da505c2f91bb01d55cb2f75f0aebdfa37ed10d8a8102165adf87d03b421e604231ed200d" - deposit_data_root: "0xba13491453796254686b89b3a1d2589d271db8a73cf2420df2f1d7a232c69238" - eth1_data: - deposit_root: "0x566f2e0bf068a861f4bcbe154f442dd6ae9097bbf4270c7dbfb3221ae6957fde" - deposit_count: "466" - block_hash: "0x29affc9ff69377a80f0888246a6bc7c98e7b914e4d6ce935f33e017fbb119519" - block_height: 467 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x4f223a1f874097defdc9227f99dd60128d5695ed143768671db7ea87ff207409" - deposit_root: "0x566f2e0bf068a861f4bcbe154f442dd6ae9097bbf4270c7dbfb3221ae6957fde" - deposit_count: 466 - execution_block_hash: "0x29affc9ff69377a80f0888246a6bc7c98e7b914e4d6ce935f33e017fbb119519" - execution_block_height: 467 -- deposit_data: - pubkey: "0x801125a4149f3b2ae29f7745ed91492ebd9de91da253f0a267f864302044310c1f9decede6b1b5a9e1719c36841aff5d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa4fd707ca74abc5be01c340ddad32ac490f164119e323928247e7ce5e846f31c7642270acd7cb26b220e1f957a2e1230e4073d742bfc38dc538ed5089e89d1765c8c459f2d0997357d548e797267db7066f91541415aa769fb3eaf964465861" - deposit_data_root: "0x344061e3131a0fd07c4f8c46d4e68950c9e0226907b418e7485009a1efca42e3" - eth1_data: - deposit_root: "0xf92d82cd5cfaaba51447264da8eed8cd65da770562dcedd00ec05ff6da207892" - deposit_count: "467" - block_hash: "0xee7806737892c8ba330f1d4545aacbee5f0a4b25c44d596f5eecf419ca936039" - block_height: 468 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x4f223a1f874097defdc9227f99dd60128d5695ed143768671db7ea87ff207409" - - "0x344061e3131a0fd07c4f8c46d4e68950c9e0226907b418e7485009a1efca42e3" - deposit_root: "0xf92d82cd5cfaaba51447264da8eed8cd65da770562dcedd00ec05ff6da207892" - deposit_count: 467 - execution_block_hash: "0xee7806737892c8ba330f1d4545aacbee5f0a4b25c44d596f5eecf419ca936039" - execution_block_height: 468 -- deposit_data: - pubkey: "0xa7852f47c6391e4ea0854d59b4a69ac72be49f7f798c19146a787dcc25a48a183ac3902001d0225f242ced6cc09aa378" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa0a90f6758d780721ada83e733fbda997001304b093517bb175d9b50f903d366f16ca33f47d716197317f81326688aac0c99be675058e687c2391016f28451c82484faa49311ca5b5af7b73e68003ec0b6f10a03a5fb0d735e32a6319bcaee81" - deposit_data_root: "0x9da6faf501a235e8957b782d919b8b63d469f9cf252b5786fd8ee778e291a127" - eth1_data: - deposit_root: "0xd7166c06e1e85afaa7ae992a6aba05a63e6017f6be9acc4bd43344807cbda528" - deposit_count: "468" - block_hash: "0x29e18f49fc40f155ff1bd424b6af69c1bb1146f5e5060fc5c9c6badabb52ec28" - block_height: 469 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0xb6a8aae06157a95d5852c69b8815034853daddd3aea1c41b6082f625f64a6688" - deposit_root: "0xd7166c06e1e85afaa7ae992a6aba05a63e6017f6be9acc4bd43344807cbda528" - deposit_count: 468 - execution_block_hash: "0x29e18f49fc40f155ff1bd424b6af69c1bb1146f5e5060fc5c9c6badabb52ec28" - execution_block_height: 469 -- deposit_data: - pubkey: "0xb0c1eb313f3894f5f19ddd7cb3472f015ccf51c24bf4316fae3e8502d3b65132a8d2ac58f7d17eec0a644ef31afda14d" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaf2982541a4ee7cd7a39ce1f4dd094b64bbdf93763ae134a09c4abdb5dad4830b772b610dd54d8e7fb782de6f6e524fa0bd3dae3737afebb2a10478ba965d1619276a04a3782d3976957c60fa92cf552876ff66db974c8c4fd820a738b980040" - deposit_data_root: "0x6bd3f89d5210692fea089c2d827b529bd2c9127d4dd27b152bd78da7bd466f29" - eth1_data: - deposit_root: "0x17128858a7687c2474b0cb4f79f498a32c46a922afd92d97723efcbc332b93e2" - deposit_count: "469" - block_hash: "0xc50e70d8f92b0410c880a034aca2ebff5f6b2bd917673b1b7cb095be4b7c033c" - block_height: 470 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0xb6a8aae06157a95d5852c69b8815034853daddd3aea1c41b6082f625f64a6688" - - "0x6bd3f89d5210692fea089c2d827b529bd2c9127d4dd27b152bd78da7bd466f29" - deposit_root: "0x17128858a7687c2474b0cb4f79f498a32c46a922afd92d97723efcbc332b93e2" - deposit_count: 469 - execution_block_hash: "0xc50e70d8f92b0410c880a034aca2ebff5f6b2bd917673b1b7cb095be4b7c033c" - execution_block_height: 470 -- deposit_data: - pubkey: "0x8c6ccdc900d78ef20d5b22ad540e73864c858baf068349dafc397cd31c571708ec6ee6afef9e5711a587e3e97f1a50a0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8d02c24dbaa0a13109feddc00dba3ecdbccf6f9f89ea859ce8293f131679c24786317f96ec390c847084f7e8ae5f56060b45fda9cbdfd4219e2806f0c2e340947fcb2860ab279860360e936b31e27fad05a4ffbbff514432e3f15f9ac7fbe5b7" - deposit_data_root: "0x63bc1e79593a6bc90ad3e76e5cab85bbf3ebc0f95993633ee2070133bb805ee8" - eth1_data: - deposit_root: "0x0114581f1558b58a8fda8d0fd4d9c8d81e3a0a44e1ee0da77b688940dfcae870" - deposit_count: "470" - block_hash: "0xc72dde0d8a48244290c055cdcac8a82a10a5c83768430f4216a7f0db007f65bc" - block_height: 471 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0xb6a8aae06157a95d5852c69b8815034853daddd3aea1c41b6082f625f64a6688" - - "0x1b129cc8ae0d0bcf87188bb66ce17acc25350f28de10249e179c868a9f7a4a79" - deposit_root: "0x0114581f1558b58a8fda8d0fd4d9c8d81e3a0a44e1ee0da77b688940dfcae870" - deposit_count: 470 - execution_block_hash: "0xc72dde0d8a48244290c055cdcac8a82a10a5c83768430f4216a7f0db007f65bc" - execution_block_height: 471 -- deposit_data: - pubkey: "0xa8aa5faaa4bbda5b5e7d610746f27c09bdee3a3f057550bca600ebe1c83749525a135829669a0162c3339ea40dc5525a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x94fb954aed2ccc71dbeac75d03bab4f59bc09a2468b25a12ccda4d50efe9bf2ec3e4188ecbe50c1bee58ee2d90acb83b116085775618887034be7c1487e52e76d2aaa2fca8fc37ebbbf552d4a5996d086fc653b447337a89bcc0ff4e678b2e94" - deposit_data_root: "0xc9ed8e05a9be23ad58521dfeda162c75bff62477ac3aea3f373e1c12c5fe5ce7" - eth1_data: - deposit_root: "0xc05d8b31462806679096a4b5e89c1f82e0c237f65cb53f9dbd7086517aef4321" - deposit_count: "471" - block_hash: "0x9923a0a46a2f622ff2ada864cb8ce46be4ad5ac7fd386ee8c1dc2df41279395a" - block_height: 472 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0xb6a8aae06157a95d5852c69b8815034853daddd3aea1c41b6082f625f64a6688" - - "0x1b129cc8ae0d0bcf87188bb66ce17acc25350f28de10249e179c868a9f7a4a79" - - "0xc9ed8e05a9be23ad58521dfeda162c75bff62477ac3aea3f373e1c12c5fe5ce7" - deposit_root: "0xc05d8b31462806679096a4b5e89c1f82e0c237f65cb53f9dbd7086517aef4321" - deposit_count: 471 - execution_block_hash: "0x9923a0a46a2f622ff2ada864cb8ce46be4ad5ac7fd386ee8c1dc2df41279395a" - execution_block_height: 472 -- deposit_data: - pubkey: "0xa7c107c72118446d1dc3c5c1c42f95ca2471dddd61c1461dd8ce028a1dae7656c98c3eca5f6dbeae8f9594e3e652a4d3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8cec6a4e726448a24c2f9f4e1f82366bd723596c8d4a62be7a2c8a0e0a513518b9cd9cd00a13000a1d03a5bde2336c920752aa634d77143f890ab2718ecaaa5527fe7ef3b2489ff7548af90eefb857c03b9bf52dc9089989387c8be9141dea65" - deposit_data_root: "0xd2602c538061473ff8ada72bccc5cb574ea9811b109867a442ec621a78184249" - eth1_data: - deposit_root: "0x71d80a6c13aaf2af9b142d5f75859f7f86cf33492301a62d42134810c1a43862" - deposit_count: "472" - block_hash: "0xd4b509039b3e7f5664ed77eed00addbc90392021394ae1c431c10f9236bf539c" - block_height: 473 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - deposit_root: "0x71d80a6c13aaf2af9b142d5f75859f7f86cf33492301a62d42134810c1a43862" - deposit_count: 472 - execution_block_hash: "0xd4b509039b3e7f5664ed77eed00addbc90392021394ae1c431c10f9236bf539c" - execution_block_height: 473 -- deposit_data: - pubkey: "0x899eaf183acd5beee0899a58c8ee5fff1dc76df143028d9871d231e8a3999d92098538d070fd1c9db5d9db60f23dfd4a" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x88a116fece26c6e4d209321a5c58a8e09e01fe165703b042cb577060a8bc3255a281bc960e65ce253e66d15560e38efc10fadf7f49224583d4d25b34258d46ffd6402356684954e33849e968ce77d484307dbcd7d02a6eaf75185c7512231add" - deposit_data_root: "0x57f93038b3cac9f76cb61db2309f1a5853dee6b9f4b3e713c34c4cd2a18631dd" - eth1_data: - deposit_root: "0xdc3672b4162de2f668f5865ee0f8e00e755777968de8c44b3320c92592c8207a" - deposit_count: "473" - block_hash: "0x947b126441051344edc68b986f9b345352a4f0420b44dd06a6359204d5d0ac4f" - block_height: 474 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - - "0x57f93038b3cac9f76cb61db2309f1a5853dee6b9f4b3e713c34c4cd2a18631dd" - deposit_root: "0xdc3672b4162de2f668f5865ee0f8e00e755777968de8c44b3320c92592c8207a" - deposit_count: 473 - execution_block_hash: "0x947b126441051344edc68b986f9b345352a4f0420b44dd06a6359204d5d0ac4f" - execution_block_height: 474 -- deposit_data: - pubkey: "0x8771579633474fb50075b4c801124f63c4a786e7a618bc50c00122f4fe8c18d1edc123ce9be8436b9bc8159c5d2061ec" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb208ae48bba24e07a4d4dd1465cd92f135487a71353e79657250e66e18e31c08477d6b3f94cd8836b18ac1d6d154fbb50bfc5fb9e261c5117b30a96dbb3ae0dbcfd912b465b1b0c694d0030c97ed3b40437c0559fe95bd2cfad07706345e302c" - deposit_data_root: "0x70e6082f9f4c9f7cb7c0d946caac7dd325787fe08c2a6fde05ddc7ee71bc1978" - eth1_data: - deposit_root: "0x1e2891528f3695be75c16e6f8e948737a6be58ec77667c41f70309fe2d0a0c24" - deposit_count: "474" - block_hash: "0x05b249730f2706e2825a6c3beb0582bc9430679dae5fa24c864a246efcb97eee" - block_height: 475 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - - "0xff8e34dd64f1d9e20b42622f7dd59ce1f29ae93f52847609c7fbedd88e764275" - deposit_root: "0x1e2891528f3695be75c16e6f8e948737a6be58ec77667c41f70309fe2d0a0c24" - deposit_count: 474 - execution_block_hash: "0x05b249730f2706e2825a6c3beb0582bc9430679dae5fa24c864a246efcb97eee" - execution_block_height: 475 -- deposit_data: - pubkey: "0x8416902c51bdb3933ff4e87558121d3fbab9f116f9a5b0ae9cc30c4d145c39dc988f5c0b754da3daeec71fa68144f9b3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9402807f43da37ca29cede163b91633fde7614843314b423db475c86533e0006cf6b1e80a93dc04aedb919afdd9103b617e136915062a66211720207f6b1300da29a44c751251ed097a204b44780275514318e4cf73ad1971b10eebdb3a08aeb" - deposit_data_root: "0x3ecd4d4c9a0d1810d3599f12fa62a3085f96dec792ed006f37bc2e2a20d5e90c" - eth1_data: - deposit_root: "0x04f3ebd098dd6485ad2ad107913118691ce85da75625af5e6fb730e536d298f9" - deposit_count: "475" - block_hash: "0xb63ee91d3c3edb9a298101f2c5c15a0dcc5fe27f0b564a620e944e7619845b1a" - block_height: 476 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - - "0xff8e34dd64f1d9e20b42622f7dd59ce1f29ae93f52847609c7fbedd88e764275" - - "0x3ecd4d4c9a0d1810d3599f12fa62a3085f96dec792ed006f37bc2e2a20d5e90c" - deposit_root: "0x04f3ebd098dd6485ad2ad107913118691ce85da75625af5e6fb730e536d298f9" - deposit_count: 475 - execution_block_hash: "0xb63ee91d3c3edb9a298101f2c5c15a0dcc5fe27f0b564a620e944e7619845b1a" - execution_block_height: 476 -- deposit_data: - pubkey: "0x83f8eb02f40f6369cb8836d521390b06ba91c72618056cd103ede2b6204069ff94c7364c2f24ca1db500ef627be3f1b2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8d236cee7ae8ac4a4b3bc5d71e8f1ef916dac52c731bd0992c3fab191cf2aba1e56bd69b708278715248e63d8f8b8724174c05b8ec955faf684b353c7c29c0e2bbc8b045c62c2554c3d5679e932e7ea6415999140f48860c3977c7149d5565b5" - deposit_data_root: "0xc157aa44092165b3935d971c9835801dc66b24f54c8d4c387afcc14e2b469204" - eth1_data: - deposit_root: "0xe7fcb3f12d9fd24d0ebd089fad35d0a4edd4dca4fad112f72a5a42bf313e54fb" - deposit_count: "476" - block_hash: "0x546c6d2e65b628e3015ee8c2d0494a9a20cc098ce586d738f6de069136472497" - block_height: 477 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - - "0xb99e45a812c5eaffd1f6ccb925e913d782b51cd8c371b11727ad48cc0de95d33" - deposit_root: "0xe7fcb3f12d9fd24d0ebd089fad35d0a4edd4dca4fad112f72a5a42bf313e54fb" - deposit_count: 476 - execution_block_hash: "0x546c6d2e65b628e3015ee8c2d0494a9a20cc098ce586d738f6de069136472497" - execution_block_height: 477 -- deposit_data: - pubkey: "0xb08c6eef8be13656c3963726dc3be95a5087e363fd5e72301322ea73a8840520d51f142cd9d94d0da3b7a6f22d111fd4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x974be67dd9641425535b8630a502c72298b86a579adb8f22d77474d3679ec0d3771190515ffea3cea288c5a401f275aa1951f6a3f226f9d0afd58a12520a51479a4ff168d782162ac242169580463e1e6968609fe0e911fdfe39192c816a3a03" - deposit_data_root: "0xbafe7c7e5e984655b91e5c85b2a7064664c21483721e8d534bc04ce8c1849a1f" - eth1_data: - deposit_root: "0xf41c3d61184e6a446360ecb29eada4910b0694a5f26a281fcc7c07abb81f2f04" - deposit_count: "477" - block_hash: "0xbdb7e08ed1e7f61ee28337d7842866c1b545937e7c247d895e8ba87ac1b87cc2" - block_height: 478 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - - "0xb99e45a812c5eaffd1f6ccb925e913d782b51cd8c371b11727ad48cc0de95d33" - - "0xbafe7c7e5e984655b91e5c85b2a7064664c21483721e8d534bc04ce8c1849a1f" - deposit_root: "0xf41c3d61184e6a446360ecb29eada4910b0694a5f26a281fcc7c07abb81f2f04" - deposit_count: 477 - execution_block_hash: "0xbdb7e08ed1e7f61ee28337d7842866c1b545937e7c247d895e8ba87ac1b87cc2" - execution_block_height: 478 -- deposit_data: - pubkey: "0x8374c6a0d41b2da67f26a62120128838ab64f3928769a4280d0650ff18d6262b584e8f68a915bf8e4d1f6e18d72a6ecd" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2d63f5fd222d3c4db2c0f7bf4875d7a98cbd9511a43cc448df75d682984ed16035d3a9ea512c8da123d78400a29751910aeb767cf9361e2af7d50f4680796c3fc7a499028508c1de8093f6c4a9faa2d2e8737e39e90efc7b7f6304c79e471a9" - deposit_data_root: "0x646da1db4ebfa44f98a74e708b6834ef0b3547d3ed62f4aa5ea28922b9ceb10f" - eth1_data: - deposit_root: "0xbbae20cfeb909f6a6c3efb0bdbf43a91e0111ac12305726d7c6ff78f0337edeb" - deposit_count: "478" - block_hash: "0xdad4caa2c941c49d2f6f9f8dba86a411d4cad65a2a89c6a12d017f08ba5bd381" - block_height: 479 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - - "0xb99e45a812c5eaffd1f6ccb925e913d782b51cd8c371b11727ad48cc0de95d33" - - "0xe4c5f8b27ed915c009a548dee4da3ba212b3c42979ac7fa6c80abfe02d1172dd" - deposit_root: "0xbbae20cfeb909f6a6c3efb0bdbf43a91e0111ac12305726d7c6ff78f0337edeb" - deposit_count: 478 - execution_block_hash: "0xdad4caa2c941c49d2f6f9f8dba86a411d4cad65a2a89c6a12d017f08ba5bd381" - execution_block_height: 479 -- deposit_data: - pubkey: "0xb98aeef39fb7eecc46e6cff2632a543757a8bd80bebe409b5d0eafe2922583040749cee9f130cf12eebf85c1dfc0adb4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x85fb8f084baed303cafc16887d1fd22b1ae16cf9a6978a9bdbea426975b183896a1ea8731b3ad9a61e0211519123cda50cbf3814f821938b9365ab4058228162aa1448881c64f410e15f09de147be4076ab418319748f97c09a57e18f3117370" - deposit_data_root: "0xa196eee94becd475763319784c5f9a7ba0854e82893a0d9cf21d4952e08dddab" - eth1_data: - deposit_root: "0xb491c8ebe5be3cf6e59268e03ebc32a74e64efce2c805e1b328d3d2d8034af48" - deposit_count: "479" - block_hash: "0x56ca5f2c24ea7f40984bc5b6c97b289b23e33c6199d917b0dff9fa2e12607b60" - block_height: 480 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x539c3abb99dc0d3652e3032b3abdd4f5d531d3783ac3c97c10e77108952d53fc" - - "0x399784c504476260102e50dbb4284f4f83d3acc78686434e89711b29ffb00b2b" - - "0xb99e45a812c5eaffd1f6ccb925e913d782b51cd8c371b11727ad48cc0de95d33" - - "0xe4c5f8b27ed915c009a548dee4da3ba212b3c42979ac7fa6c80abfe02d1172dd" - - "0xa196eee94becd475763319784c5f9a7ba0854e82893a0d9cf21d4952e08dddab" - deposit_root: "0xb491c8ebe5be3cf6e59268e03ebc32a74e64efce2c805e1b328d3d2d8034af48" - deposit_count: 479 - execution_block_hash: "0x56ca5f2c24ea7f40984bc5b6c97b289b23e33c6199d917b0dff9fa2e12607b60" - execution_block_height: 480 -- deposit_data: - pubkey: "0x8f9e00f60b9b830e72b7d0b45a3dab2cda1f678880db3b5dc5107e07850dc5146afe8b0999102d4a8bca5cbc27cd8a2c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x967c3931f641a1b00bf1d2f1f05b0d39470d3a18009ac4069f5b69f8c3d90129c10d5b0cf81fde69ae27470a47834d3a0946c9d6edbfbb3386910d3e881a34dd9b58ee1c51b34bf3c60920c52d449adcd850fe9a3e662e449cf50e02ef80eeb0" - deposit_data_root: "0x1d584d8969a792467db268a84b71823f5421782407a24814519c5480593bdf72" - eth1_data: - deposit_root: "0x134da8281281e224a0ffb96b845d7792e22fcedd065ef136210928ec2804fc8b" - deposit_count: "480" - block_hash: "0x0c9865641090c264e1d7cbc1b65b0faf1005121540dc51869caba029aec61f22" - block_height: 481 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - deposit_root: "0x134da8281281e224a0ffb96b845d7792e22fcedd065ef136210928ec2804fc8b" - deposit_count: 480 - execution_block_hash: "0x0c9865641090c264e1d7cbc1b65b0faf1005121540dc51869caba029aec61f22" - execution_block_height: 481 -- deposit_data: - pubkey: "0xb42800735102f10b3e09c11e9113e0f4955bd19d0c5174f587d446de44230b10c3e8b439fbfc2e47f9e82ac2ade917b3" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa26895838394e4ead46ee443e6e76d42af792263bdf42113c206e99cde1d5ec5784f2a072691e1bfa9e29664741c3944009fdad153287feb4c172dde4ac6b63825c4211f2c09c0ca619f8c83b2c82a618aadfc27058df67685c437033cb23e1b" - deposit_data_root: "0xc2a1be8de10512de15ce813468cadd14fec5f4dcff841434ef8ac94b0d4a1e8c" - eth1_data: - deposit_root: "0xd45d33b559eb5e75ad5e92d049659ab27a2c036a7aa315ec40fbcf807a8b78a2" - deposit_count: "481" - block_hash: "0x8c9122b92253122ed84adea540862612634d94e7c00df5df311278d785176f1f" - block_height: 482 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xc2a1be8de10512de15ce813468cadd14fec5f4dcff841434ef8ac94b0d4a1e8c" - deposit_root: "0xd45d33b559eb5e75ad5e92d049659ab27a2c036a7aa315ec40fbcf807a8b78a2" - deposit_count: 481 - execution_block_hash: "0x8c9122b92253122ed84adea540862612634d94e7c00df5df311278d785176f1f" - execution_block_height: 482 -- deposit_data: - pubkey: "0x877968c73d43465feea583c7bde8270773fbcdcf9d1e3e4fe492a208e788bb56b329174b0d2539d96877e9d6f5bf1b41" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x83a31f50234740124160bb83287570c689c12df4415aa7d784fee273425568f566481089d6b910c6477cd982601d9b381333fa40c666da37750771f365a4998d6f2e1c5eb8412df44a7d4038b5528b3a624cc0af754e79e6e310e8669cef79f9" - deposit_data_root: "0x5796c508baf67aa0316bd134dfd20381d7ccaa37735914bf83f122dd762e5ca2" - eth1_data: - deposit_root: "0x9d0ac2b2a873b08b17e722eb0893f30e4518ad523ee623d0567aa82dabc6dd32" - deposit_count: "482" - block_hash: "0xfde6c5dfbc17f3071be1020ace406b179a5e30640bc33c304b8e8264719aedd7" - block_height: 483 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xdd4de29f488ddda22d81e4e7a3e890a1c2fa2f4602bf5612e8af70c852b459cc" - deposit_root: "0x9d0ac2b2a873b08b17e722eb0893f30e4518ad523ee623d0567aa82dabc6dd32" - deposit_count: 482 - execution_block_hash: "0xfde6c5dfbc17f3071be1020ace406b179a5e30640bc33c304b8e8264719aedd7" - execution_block_height: 483 -- deposit_data: - pubkey: "0x915fdf1928be84527b07ceccbc4a278f1e9e010d6f716d8c3a4666bf649411744355ef25b22276c2ab51b3af29c67119" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa3624f6211be2fcca00aeacae32fbc4101e787724ccd33d7293053b35ac8364784da5202037343024f4c94c86ce0a0ba171efaa129810f463e6f1337e59bea4aee8da537b313ccf72d207859d51bc2bec6c585e2598747b0b7e9f824ffb9f75b" - deposit_data_root: "0x823ace95fa80327a404d8bc47116e2f39db4807f1547044d2f855459a6910f88" - eth1_data: - deposit_root: "0x34065c8a5baf250ed894b10d34c0b32b892d3457967219896e660caab4900265" - deposit_count: "483" - block_hash: "0x15dafdb4863cb94e4f40272996afd55a2772e370e89b8a75114fbd6706099b96" - block_height: 484 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xdd4de29f488ddda22d81e4e7a3e890a1c2fa2f4602bf5612e8af70c852b459cc" - - "0x823ace95fa80327a404d8bc47116e2f39db4807f1547044d2f855459a6910f88" - deposit_root: "0x34065c8a5baf250ed894b10d34c0b32b892d3457967219896e660caab4900265" - deposit_count: 483 - execution_block_hash: "0x15dafdb4863cb94e4f40272996afd55a2772e370e89b8a75114fbd6706099b96" - execution_block_height: 484 -- deposit_data: - pubkey: "0xa99b23673a9c02a8da9e44fda564aaacabc12fac43b600884e06ff21ca2ece8fb8ca28c593d3966ec6ff427f8f497c05" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa2e5bfd6972dc7e1224cd3a6cb8eb2120f88cb43912bbeac84e359e10ad2c707c6318c1a9edda7f10e0ec8a7c41af9e613fe59d07f50a3f5027e63c11b391ed425d1c6ab7ceea8681f310d34fe9b2c658c81472c30f89a6034d9f79ed5979b53" - deposit_data_root: "0x726ba60cf4b669785203993565bb3b21592a22fdd92ed204db8774bd95854cf9" - eth1_data: - deposit_root: "0x5a94789da4c0fa8f904274e1a6be5fe01019d467234f560932302dc4480c9207" - deposit_count: "484" - block_hash: "0x5978eac6de2f57ef776f150ff7337be8d7323810da27f674804c0e2c34322109" - block_height: 485 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xf8fc1b0c4abcf5d5c5214689c52a6c792ebb882fa93e6cdd79998c5bd752a4e1" - deposit_root: "0x5a94789da4c0fa8f904274e1a6be5fe01019d467234f560932302dc4480c9207" - deposit_count: 484 - execution_block_hash: "0x5978eac6de2f57ef776f150ff7337be8d7323810da27f674804c0e2c34322109" - execution_block_height: 485 -- deposit_data: - pubkey: "0x8759613fcfec38cf67297fc0b956382eb403f1b9126d1de3a26e0ee7142346d5457f09c7765d82d49f51726293b3cf7e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9924c8222eea8672b5aa21d38fca95cdaecadda51e373cd60100f1084e96140d333d2b01f60308bc8ca3ece43f1f915311b478bdefe9302188d636d3321ab684530f32cd8fa62b2ca575e0d3ac6b163a1934dc5e702af08253dd68c61f039e4a" - deposit_data_root: "0x9613b659ffe2c9842167aa76d78262e1b72c7815b2751f4e9abc40bdc833a793" - eth1_data: - deposit_root: "0x301a60c707ce54ecd68bf6ab842130f5687966e5e3d6802c98a93105b8fffa6c" - deposit_count: "485" - block_hash: "0x33d4c030649788458fa0fc39cb615cac9e8614791d012c471b8f2c45314a398b" - block_height: 486 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xf8fc1b0c4abcf5d5c5214689c52a6c792ebb882fa93e6cdd79998c5bd752a4e1" - - "0x9613b659ffe2c9842167aa76d78262e1b72c7815b2751f4e9abc40bdc833a793" - deposit_root: "0x301a60c707ce54ecd68bf6ab842130f5687966e5e3d6802c98a93105b8fffa6c" - deposit_count: 485 - execution_block_hash: "0x33d4c030649788458fa0fc39cb615cac9e8614791d012c471b8f2c45314a398b" - execution_block_height: 486 -- deposit_data: - pubkey: "0xa0ab65230a0c9a2e2ec267918366deaa93d80068b14e691de2dff126c604e755b72d0865099078870d6a3d47a2f56d14" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x943d8cdce9bb776c6713cc436e6fe6dd9d3d6f015d79153945078cb204f725192e2bb9c95505b813782265e00532110b10c9c6d08012ead8d3d3ac825dee975cf68caa47a6a83697fec5f753de0480a2c81ec6799f3b9fcee7b561bdf1cd27e6" - deposit_data_root: "0x02b77bbb8e3d988f7daa40bb4179390f620f74e3f370ca75e40087e188cfb290" - eth1_data: - deposit_root: "0xda801f823b9de4864cb8fbac530776a3faf03d59b3a2dbc736799f843ff769c4" - deposit_count: "486" - block_hash: "0xf40b8bd6aca0d2df5d30673cb93799ab391c73417d9fcd66c0f36061950258d7" - block_height: 487 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xf8fc1b0c4abcf5d5c5214689c52a6c792ebb882fa93e6cdd79998c5bd752a4e1" - - "0xc40108b2f8489c8b37335014f19abcb82e4493969e2c8ec160e771f38a2ac79b" - deposit_root: "0xda801f823b9de4864cb8fbac530776a3faf03d59b3a2dbc736799f843ff769c4" - deposit_count: 486 - execution_block_hash: "0xf40b8bd6aca0d2df5d30673cb93799ab391c73417d9fcd66c0f36061950258d7" - execution_block_height: 487 -- deposit_data: - pubkey: "0xa76186ca2743b7149ab7b5ab83c843d2d724ed85ef13a9e0e23a697ba048cea17acc68d9dda73d1f4ef97b9747e491ec" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xae8ce2cf4550895297567df5bbe471d3c58caec5fdfb1b526f4f686c0d908b32e3c88e0905344ce7d6c5ccf7d24ac2820da54a60ef97d5ad62f3de61b3f3b5252294b280efb80f24c50c2c9b1e0775b8e263170b373b876051e34e2e8bb14a0d" - deposit_data_root: "0x992ec5657a0236c36cdeb5c71669678c8d28314815f3f267ee7791968f93ee5a" - eth1_data: - deposit_root: "0x178c069965cd1da6422db237305cb7d972195a3320cb2e863a3e36aa0d7a29e6" - deposit_count: "487" - block_hash: "0xfbc23345cce8edc158f0ca53ecd03de7f09bc65f648cce073dff8588a7195f0a" - block_height: 488 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xf8fc1b0c4abcf5d5c5214689c52a6c792ebb882fa93e6cdd79998c5bd752a4e1" - - "0xc40108b2f8489c8b37335014f19abcb82e4493969e2c8ec160e771f38a2ac79b" - - "0x992ec5657a0236c36cdeb5c71669678c8d28314815f3f267ee7791968f93ee5a" - deposit_root: "0x178c069965cd1da6422db237305cb7d972195a3320cb2e863a3e36aa0d7a29e6" - deposit_count: 487 - execution_block_hash: "0xfbc23345cce8edc158f0ca53ecd03de7f09bc65f648cce073dff8588a7195f0a" - execution_block_height: 488 -- deposit_data: - pubkey: "0xa70362ba9834a49f9f1ffdccf221fc2eb1f7a5cb584bded1d79c44e7e7890ab28d787004c5cc6a45d0e941ae27f1fe39" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9840593dfd806431b6892e7c3026ea49084c25df871ba5dc8c9a28d5b2f8c9bd5379233c68d4af650f4409c6c5d814dd1337059655b61267c924f0f271eb7284702eff667a9ee4ac2994fef2a8c601d32a3ab39406f31c55976b824e5569f2c0" - deposit_data_root: "0xd5b2e76b1095e39dc9cd525aab3f0ad77b8e3210c9950cffd3247cca2c2d93d9" - eth1_data: - deposit_root: "0xacc5e8c62a70a8b48ea72c2b7dcf7e81d33b98d0e7299f96830c4c9a87bc5b12" - deposit_count: "488" - block_hash: "0xe7952006db942a66abc5319d9ce249354eada481eee6cdaf97fe348f928dbd3e" - block_height: 489 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - deposit_root: "0xacc5e8c62a70a8b48ea72c2b7dcf7e81d33b98d0e7299f96830c4c9a87bc5b12" - deposit_count: 488 - execution_block_hash: "0xe7952006db942a66abc5319d9ce249354eada481eee6cdaf97fe348f928dbd3e" - execution_block_height: 489 -- deposit_data: - pubkey: "0xac66effd5784f677ff6fa5cb96d9cf14552d5888a8cd78ff54581b9fb35d3980c9b34cab41f565288812d77e3a44cceb" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb7d4c0669a88e0f84cfc9c5d53bf41d7703bed37597ccc66902f8d8271e7a595b2f170c13072e25c9630384a5a9070cc18a783226667704a6858fff0e77ff24dba0df19aa8ad71056fd18598dce161e4d547c3f29190803204de160e859af69d" - deposit_data_root: "0x952612c181f310ef8c2b53d2022d08549f4d21d9d0d603d44b2bd3809e5b333b" - eth1_data: - deposit_root: "0xaee05275977d88ab3de85c1ef629864f36fa9ad8bc59500eb34e56ebfb724130" - deposit_count: "489" - block_hash: "0xbb1f6f7c565f47fa6e524ee0901d2b8b7a402895f4ccb7ee210723d757715869" - block_height: 490 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - - "0x952612c181f310ef8c2b53d2022d08549f4d21d9d0d603d44b2bd3809e5b333b" - deposit_root: "0xaee05275977d88ab3de85c1ef629864f36fa9ad8bc59500eb34e56ebfb724130" - deposit_count: 489 - execution_block_hash: "0xbb1f6f7c565f47fa6e524ee0901d2b8b7a402895f4ccb7ee210723d757715869" - execution_block_height: 490 -- deposit_data: - pubkey: "0x933146088cae286d7d0c2759fab13c1f3e7cea13c05504c006e2ac25f322a779905fe4466f0db965620767d6270ac324" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa8a74b8afa78efaabefa3316ed4fb2715e2be3c0bc70b3df6b04c13f8d19ffe08df3558a083a1b34ceb06fb44c437cc9189378d8a785f2a3ac68606189d06f3787944c2b28f249bbd0755ffc2772f523a5cc2502a89f52be0d225c7330d9f478" - deposit_data_root: "0xbc62013d7afb2e7f7c9c306c73449cef0f5b76e80568d3efe50744b3586c3c6a" - eth1_data: - deposit_root: "0x41dcb927c029b48451acb6f81bb177f21ec50556bce33f91939011f29fc834fd" - deposit_count: "490" - block_hash: "0x0af4b9127af4de48537331081e5d471983a3787901800be316b3060496e718dc" - block_height: 491 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - - "0xc2117c26af280c0c49598e0448112e04588ab6a3ee01b2be0a1380ccd6ab884a" - deposit_root: "0x41dcb927c029b48451acb6f81bb177f21ec50556bce33f91939011f29fc834fd" - deposit_count: 490 - execution_block_hash: "0x0af4b9127af4de48537331081e5d471983a3787901800be316b3060496e718dc" - execution_block_height: 491 -- deposit_data: - pubkey: "0xb31a6cb5ab165f4b7b66bee6fcab77e410a3c7d7049ca5a6a7bc64b30a1c49bef4a1ed72454b87c8b879c00c57212ac2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8b9393ad8fe5132742cb3f2cfc722f9f80c98dfec7055a09fe4d9ee744c484d5ab234d1be89e8686ab6173096be867340b7d4c8ec2b76a4974a5e4f75664fcf6a82f95089fa8d284296a9e7608a88e8668974abfc58c0cc5552bdb0633449312" - deposit_data_root: "0x38e2586880e59ae2b10e79b4bbea1bbf4fff9eb24c0e76e9eeff1117ecf7e326" - eth1_data: - deposit_root: "0xdc6d5c39bd67371b5a38d2baa1180139c09dd6e762ed5274586d6fae140aef33" - deposit_count: "491" - block_hash: "0x10a29b7a13bbba866147b26e86cb01908d83599a1dd6734fc5cbab0c8d5c2761" - block_height: 492 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - - "0xc2117c26af280c0c49598e0448112e04588ab6a3ee01b2be0a1380ccd6ab884a" - - "0x38e2586880e59ae2b10e79b4bbea1bbf4fff9eb24c0e76e9eeff1117ecf7e326" - deposit_root: "0xdc6d5c39bd67371b5a38d2baa1180139c09dd6e762ed5274586d6fae140aef33" - deposit_count: 491 - execution_block_hash: "0x10a29b7a13bbba866147b26e86cb01908d83599a1dd6734fc5cbab0c8d5c2761" - execution_block_height: 492 -- deposit_data: - pubkey: "0x9570f6171cbc78d58dd8afd84cc2f803ab049dcc15566b4c16406f798b65346f3bb36c45aff846e4604ab79ddba8125f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa93e273b50dbfbc9306d5dc86e49923e641166456f2930ebd3ca51ee1db9bc21bcfc27d90de43a76657c3f302456093b02adc0e553601ee8781f149c9c9e38021222db3614ac89f5789a9d0765bb94d03e0dc034b62a828317dd583d44a47efd" - deposit_data_root: "0x52e9547d5df15475ad6635a94d60a382d8170a9e94db26e2948dfbc1838bba10" - eth1_data: - deposit_root: "0x22824f94535edf7e747a7ffe09ee5000d430a32a5eb63a223e6e888e558d6840" - deposit_count: "492" - block_hash: "0x24b8b1fb9de52367089b9098ba02dd01657c2da749dcefc4ff56f5c68c6852e7" - block_height: 493 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - - "0x4f8311465fde082df60a4db44e53bcd965cf3a8f39d3166b2a769cf8a5cdc758" - deposit_root: "0x22824f94535edf7e747a7ffe09ee5000d430a32a5eb63a223e6e888e558d6840" - deposit_count: 492 - execution_block_hash: "0x24b8b1fb9de52367089b9098ba02dd01657c2da749dcefc4ff56f5c68c6852e7" - execution_block_height: 493 -- deposit_data: - pubkey: "0x871e9ef179706974e0423fb0a6f17673dd3d91ac9625a5c034d2797c396698489dff3c53abea15b7b5604919fd36acf6" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x84eb083fb8262d0a9d6c1ba20a5ceeee8d8b4fa4643cca03b6fd7b8affe3245c57a60294732b27b05adf0ca549bf0187040b38828218e89fa3905975e37a6d0f7c3664b271bda3a996f698fb081f98606c079016017e45388f4abfed55d5acb6" - deposit_data_root: "0x366bca78131fd8260d4223c1c7c2c4fed8d22a5f43647b8bb5692ede92d255bc" - eth1_data: - deposit_root: "0xe5cac4e82791679b4044bfa3435209aa4b6388af630191371a7851eaa79b3529" - deposit_count: "493" - block_hash: "0xe4511513af4b15a09c1f2e1fd8ca52c6425803ef329133541130331d5ea134ba" - block_height: 494 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - - "0x4f8311465fde082df60a4db44e53bcd965cf3a8f39d3166b2a769cf8a5cdc758" - - "0x366bca78131fd8260d4223c1c7c2c4fed8d22a5f43647b8bb5692ede92d255bc" - deposit_root: "0xe5cac4e82791679b4044bfa3435209aa4b6388af630191371a7851eaa79b3529" - deposit_count: 493 - execution_block_hash: "0xe4511513af4b15a09c1f2e1fd8ca52c6425803ef329133541130331d5ea134ba" - execution_block_height: 494 -- deposit_data: - pubkey: "0x879391eff950beb2720f83aa628e43b313ec26e20cad9b75457bd8de5b1883fad8c5f28533ebebac4e377d82145d75da" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8a31291a8934809c33b88d68754d8abc0c6740474264c475d2e825f550b79cc4ba4d767f152afd628eb956ab0198014e18c365a66d4e9c6d5c92c8879791a41a9940304a419fabedb6d64d58706083e94afbca69c35718e6dbc8e97ead3ce50e" - deposit_data_root: "0xa2d9df9463cd8a99c0caa53d39a2b5339149c329935288339b2dffda66babd34" - eth1_data: - deposit_root: "0x137fab1083cfe288afb0dc871b75b475b1cab3d6989cea5ec1ddc30588bcf9f3" - deposit_count: "494" - block_hash: "0xf5faca75aad4bc3195af3ce6e8cc953c804344697267f9621dc4d3d18d919ad4" - block_height: 495 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - - "0x4f8311465fde082df60a4db44e53bcd965cf3a8f39d3166b2a769cf8a5cdc758" - - "0xa3645b2bae71b310416e701a535d888cc14e78d867e2e88703a09b93af06de0d" - deposit_root: "0x137fab1083cfe288afb0dc871b75b475b1cab3d6989cea5ec1ddc30588bcf9f3" - deposit_count: 494 - execution_block_hash: "0xf5faca75aad4bc3195af3ce6e8cc953c804344697267f9621dc4d3d18d919ad4" - execution_block_height: 495 -- deposit_data: - pubkey: "0xb03bdf830d2cf1c1d8f0bb100fb3d4d399b48ca2e67e8f8513809bdf49beeb4b710438d64bdd9f4d4aebc3cc844e9302" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb81d217f78655216903e2c617be8cc10455b5f58335f522a91c35329693d5af7e906e490edd919f63d74c27b142570bf1407f26517a16835c18f6032683939359fa721e3f9ed56b8a38aec6aa36d4fa344e80a0cf16f0c990bba0d4e1d9758bc" - deposit_data_root: "0xd4872c8e2cd8f5fe969e07d8c702201df480500077039ef03baa524e4eecb5d5" - eth1_data: - deposit_root: "0x89ee072c31cf8d3c38c359eae18f99047dad53e040e404ce65f758d8973dd831" - deposit_count: "495" - block_hash: "0x2a4557f04eda1cfab40813fbfaa9d0cd3297311d4c35f56aa325e83b44667b4f" - block_height: 496 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0x3875f467344ea7151aaeaf8d2f3c78cf2dacd91f8a81b6ab8ca1e02df2fff953" - - "0x4f8311465fde082df60a4db44e53bcd965cf3a8f39d3166b2a769cf8a5cdc758" - - "0xa3645b2bae71b310416e701a535d888cc14e78d867e2e88703a09b93af06de0d" - - "0xd4872c8e2cd8f5fe969e07d8c702201df480500077039ef03baa524e4eecb5d5" - deposit_root: "0x89ee072c31cf8d3c38c359eae18f99047dad53e040e404ce65f758d8973dd831" - deposit_count: 495 - execution_block_hash: "0x2a4557f04eda1cfab40813fbfaa9d0cd3297311d4c35f56aa325e83b44667b4f" - execution_block_height: 496 -- deposit_data: - pubkey: "0xadf6fceba8b405cb2a0e853da1a8db38799eaf868a91294a297f0ba4601731a438f1f7a8b15c91cbc7b4f07734d74ac0" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9055a60776e60b558d5722e5f168b13f4ba31940ad146948423a771e4973081bc99b9247ad648af178b11d1331cadd83014504c0cc289afa2c399b983dca0a01c5a5c4b321fcd615b185f982e6663dbaadb0b9222681e841c452810421ce28c0" - deposit_data_root: "0xb89408cff9700bea9c5070d4bcae6f0a4b5171f4232a341f5f6d259b15d51461" - eth1_data: - deposit_root: "0xed3c614ac36483433d060e8e8576cb18c7aaad8b5fa57c067869a892e83aff98" - deposit_count: "496" - block_hash: "0x15efa8aba9883de6c511364fc9949a2ce28fd1320914681c40cff4c7d03dd4e3" - block_height: 497 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - deposit_root: "0xed3c614ac36483433d060e8e8576cb18c7aaad8b5fa57c067869a892e83aff98" - deposit_count: 496 - execution_block_hash: "0x15efa8aba9883de6c511364fc9949a2ce28fd1320914681c40cff4c7d03dd4e3" - execution_block_height: 497 -- deposit_data: - pubkey: "0xa931ae0ef4447284cad9e27b334924088b87f489d1c1eb49883cd2f35e4cd445dfac88e564605b32d471b56b4d98a4cb" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x927f3ded1db0e263965b5dc1433649056672f337df5a66e46e23be11287c60fb5fb83cc1878af02f020a41a52a5524730965f98460e4ec8faa34cfa7f82f84f7847e163a97bde7fd6326048787a0e65e71eb49225d2e00ef180a46eacb5821d9" - deposit_data_root: "0x8526ea94647b0af12f3cda3d20987c8914451a827b81da295d625c6621de481c" - eth1_data: - deposit_root: "0x421728e94bc65613888da789ec8a68a70a9396a248416cf46a2ade5debdf71c7" - deposit_count: "497" - block_hash: "0x01e5154c23dc0820fddb99b9d1c36c7564750cb38a39c18cf3b1f2f6db3d036c" - block_height: 498 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0x8526ea94647b0af12f3cda3d20987c8914451a827b81da295d625c6621de481c" - deposit_root: "0x421728e94bc65613888da789ec8a68a70a9396a248416cf46a2ade5debdf71c7" - deposit_count: 497 - execution_block_hash: "0x01e5154c23dc0820fddb99b9d1c36c7564750cb38a39c18cf3b1f2f6db3d036c" - execution_block_height: 498 -- deposit_data: - pubkey: "0xb304ff0820279e94ad069248706d39bddb36cb1ef69e1031f2f0683c69b4e8fe5bee35a64a7d160f8f207253bf6a1080" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xad022d059131eaf12492893f3b4d531ef72f713a18b5965e2f24a30d759e997809ae16fa808210f38cab0f7b3a244a0419913fb6805654a9b0e02e426e47b2aa363b8bddcaaf36569383559214a5a79d8190f9a268a30532c3e42866e02ca21f" - deposit_data_root: "0x46c229cd0c3e568093841a1e7d92c9bfa369d9dc633067c83aee89f0cd754c3e" - eth1_data: - deposit_root: "0xdb3a62269b0ddfe5139f62b8b90c661c1f7e5e36821357e1b6e7e3288d9ec2ab" - deposit_count: "498" - block_hash: "0xcdd347d62aa299215a836ce87a9f671d2cb8dd4541325c26dfc66b5c87cf937e" - block_height: 499 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xf2b54cd30ee3995c110c2028f35d5a400cb8a1ce9600757c8bae0e426db572c9" - deposit_root: "0xdb3a62269b0ddfe5139f62b8b90c661c1f7e5e36821357e1b6e7e3288d9ec2ab" - deposit_count: 498 - execution_block_hash: "0xcdd347d62aa299215a836ce87a9f671d2cb8dd4541325c26dfc66b5c87cf937e" - execution_block_height: 499 -- deposit_data: - pubkey: "0xb51c0882970b04879282a60dcfda6577abd62328859c811ad190b4a77b824e4ef7dcfa544f7e76ad0d0c53372e9ba43f" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb67d9631d1380573c574fc1a57dfd7266a42318b253955bfc2fadb55af3eabdbb311950c219ae6ca5ea8e7a8967ba7a41519a96a7437c7fa24bdbe02ae13f7191308eacab1528639d0efd1b4d195006b63f862202175e8975fa5b7f86461f481" - deposit_data_root: "0x5342789288be2a5531cb6f31633584200615948145d3542a266e2b8965edc64b" - eth1_data: - deposit_root: "0x54c8df95f9d2b54939012347e05c3214e517c55d8540807b1ad754619c5a971d" - deposit_count: "499" - block_hash: "0x0b9574a4021e274198282a737f25de33d496573581e0912393315b491ea15ef2" - block_height: 500 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xf2b54cd30ee3995c110c2028f35d5a400cb8a1ce9600757c8bae0e426db572c9" - - "0x5342789288be2a5531cb6f31633584200615948145d3542a266e2b8965edc64b" - deposit_root: "0x54c8df95f9d2b54939012347e05c3214e517c55d8540807b1ad754619c5a971d" - deposit_count: 499 - execution_block_hash: "0x0b9574a4021e274198282a737f25de33d496573581e0912393315b491ea15ef2" - execution_block_height: 500 -- deposit_data: - pubkey: "0xa24dd1ce8d1d5af40401a2c2d47027de989313ead893a646456eb4ea9d6e38cf8cd37f75e2dfa631beed89970848a39c" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x91637e293d8b865c76aa80ccaa42e10dce7e1002647423cefa8e67c740f755a5b7b833a7b201e54a4d1a0a563963716412cb466eadd67b17d67c922d34f9f6e3c9ec1ad09434685cd0c7c3e1008ec84f4db02c3054023cea1d7716739129a3fe" - deposit_data_root: "0x721297ec667e7bfec80ee3780c2a99c345a4a4de112c6c18acf2b2f7d9c01539" - eth1_data: - deposit_root: "0x695c65690f919bc3d1a81d29b03fbe3799754db34e73dd334d614198f58e7f78" - deposit_count: "500" - block_hash: "0x525ada6ebdc963f5b0cb396229e629557816abb142eb8e8161d5cfc5b7d2432d" - block_height: 501 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xf71b0bf2bd3427f729de49cbe1f7cad35e2e80960e2f4a865c936f4197f74327" - deposit_root: "0x695c65690f919bc3d1a81d29b03fbe3799754db34e73dd334d614198f58e7f78" - deposit_count: 500 - execution_block_hash: "0x525ada6ebdc963f5b0cb396229e629557816abb142eb8e8161d5cfc5b7d2432d" - execution_block_height: 501 -- deposit_data: - pubkey: "0x943c88ab60e1b649772ebefea93e27bdc89bae1901cb8f181fb813a753861bf7e3c460b909566f30be2ad7ecc7e345aa" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb6cbee3d623fcd4a2bdf4b372ac8a54bea60d3b9a3c1079336a2250f6584b0bb0f5ff6fe074211a733ba8c0bd52d102e0ead892d2c8c7258d80048f03717498493cba834a30c966fff67b205ad6ac3a1ede20f3c9ed50305f07d3f2ac89863af" - deposit_data_root: "0x563286c1b77c9e1d183fb518d9f263071cb48a8069db366a6aeca6bd4ec35842" - eth1_data: - deposit_root: "0xb11904549318b9f97731d696ceaf2a039e35e5b779234026e50f7e8a678bbdb9" - deposit_count: "501" - block_hash: "0xa936f16a1d89a846f3001253b571b6ca0f0aba7e546382dee4a0dbcc7a63c63a" - block_height: 502 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xf71b0bf2bd3427f729de49cbe1f7cad35e2e80960e2f4a865c936f4197f74327" - - "0x563286c1b77c9e1d183fb518d9f263071cb48a8069db366a6aeca6bd4ec35842" - deposit_root: "0xb11904549318b9f97731d696ceaf2a039e35e5b779234026e50f7e8a678bbdb9" - deposit_count: 501 - execution_block_hash: "0xa936f16a1d89a846f3001253b571b6ca0f0aba7e546382dee4a0dbcc7a63c63a" - execution_block_height: 502 -- deposit_data: - pubkey: "0xa2eea192ab4a2213efc58879ce008f8684c3b8da24d7a0124ed33c6ee9d3d12adcac702a938e2ccad79fde467fa413c4" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa25236c672bf2eb19b4a8a9da4564a35e2f3c8545f4190ce5916009f0957a28784622d6840a806f29df5a27e99d58057014595ab84d62f0d8edb67e4fa91504f68b906f9a975bb62da0714929fb0cb1262345bca6e2b71d0db4bc5d7f353397d" - deposit_data_root: "0x3691a26d2ceb9f3a6417d2c53d9ed3fd5cc2fbcc1211d78ed5d2e21d7930ac17" - eth1_data: - deposit_root: "0x601b2c94d23ab5858f4eacb14fda5eeb7408d13582a6afdb24b8fc91f1ade8a1" - deposit_count: "502" - block_hash: "0x040c9bd5cc7316ee54b15126e380a8795a352165ba3eea6328e65389101541bb" - block_height: 503 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xf71b0bf2bd3427f729de49cbe1f7cad35e2e80960e2f4a865c936f4197f74327" - - "0xa3d1c3c7ccbc27ed4d1edd2579de324b7a53144a936ee798b133252cb1eb8466" - deposit_root: "0x601b2c94d23ab5858f4eacb14fda5eeb7408d13582a6afdb24b8fc91f1ade8a1" - deposit_count: 502 - execution_block_hash: "0x040c9bd5cc7316ee54b15126e380a8795a352165ba3eea6328e65389101541bb" - execution_block_height: 503 -- deposit_data: - pubkey: "0xa1792fc5047546bc98391908b80fc9b4a790018b580836cfa593426624ab1785d11449a03c93759f34645d277e9f26ee" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xaa2521a398340e4fa390e4335a1cb3fe7e840ad6da4dec4c03332250ebd2d43fb6b7a968c6c06e50389fd654b08c338a143b1ccd26fb428a0b6df28d07d294d87cb214f7be07a59cf4f5326a17b801b9d1c37e8e44930420639784b5ee777cb8" - deposit_data_root: "0x4bec8c542e2bfb99d7e4703013705c2c3192f21335f1ea00d5267e869640c31c" - eth1_data: - deposit_root: "0x3f0fbcbb62eb78578683087a8869a260b30b001bb6afdb53246538c61e686b6f" - deposit_count: "503" - block_hash: "0x5c8134c7e7bb2f1bc0781ddc9b331afd5be19587aaac6c911a617f8cdb9a6204" - block_height: 504 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xf71b0bf2bd3427f729de49cbe1f7cad35e2e80960e2f4a865c936f4197f74327" - - "0xa3d1c3c7ccbc27ed4d1edd2579de324b7a53144a936ee798b133252cb1eb8466" - - "0x4bec8c542e2bfb99d7e4703013705c2c3192f21335f1ea00d5267e869640c31c" - deposit_root: "0x3f0fbcbb62eb78578683087a8869a260b30b001bb6afdb53246538c61e686b6f" - deposit_count: 503 - execution_block_hash: "0x5c8134c7e7bb2f1bc0781ddc9b331afd5be19587aaac6c911a617f8cdb9a6204" - execution_block_height: 504 -- deposit_data: - pubkey: "0xb2f64f70753f79fc04398f220f2ee1a54d14c8bd31c1b27105aae559d5006434135e516e975f57c7a41241ac74ab6aaa" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa17e776cdecb5f4c33b2a84cd37b12bb2bba06fc0e95ec255a0722ef32b2b273d2dd47001574d9ac254c7451be09614702d2761cada37688adb3babade14bbaf03a480f771ce1bc52824f73518dc1dea9786d1dfd34407fa83974ad8c79c5849" - deposit_data_root: "0xd4b6855d328858c320b36bbffda4a196e410d8ddf105c387c8ed7d3781853ab3" - eth1_data: - deposit_root: "0xd656f74b083d9e27e3d9a38247dc8ef3a48fa128173e054d6e8cc476a07a5dc0" - deposit_count: "504" - block_hash: "0xfd8f2f9a3cbc74fc6def66bcf7362ab06ffaa26414ec2be744954381e6e6c8e9" - block_height: 505 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - deposit_root: "0xd656f74b083d9e27e3d9a38247dc8ef3a48fa128173e054d6e8cc476a07a5dc0" - deposit_count: 504 - execution_block_hash: "0xfd8f2f9a3cbc74fc6def66bcf7362ab06ffaa26414ec2be744954381e6e6c8e9" - execution_block_height: 505 -- deposit_data: - pubkey: "0x86195e4462e04f6e0f4c2545ecc9c0fa812f1ffd6cde31bef2f59c5806d839937d60bd8180c2362555f73aadf6591965" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x834363e5f9c86333ab75a6a75f31324e5789d3fa705a4cb13c86a8045c27b9a37e8648ce6b0bf3f1d1c57007e40b54d80a3cc975baff2463345a3c438668f70ba5fa4cf906f7a23687eddb0b2c46a2957ecc1d3867ff5c2462c7448112089311" - deposit_data_root: "0xfa190fba4e1bf49150b7c76cfd7d07f269d46fe04f08995bb7473345b2bb4499" - eth1_data: - deposit_root: "0x723a02ae297d5787bbc4a3a1b30783fcd486c0ccd9428873c237d78d97ecdf2b" - deposit_count: "505" - block_hash: "0xd31b757dc9ab9e121243a12efd22177616a1a0327335121b63f0d91f175b3344" - block_height: 506 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - - "0xfa190fba4e1bf49150b7c76cfd7d07f269d46fe04f08995bb7473345b2bb4499" - deposit_root: "0x723a02ae297d5787bbc4a3a1b30783fcd486c0ccd9428873c237d78d97ecdf2b" - deposit_count: 505 - execution_block_hash: "0xd31b757dc9ab9e121243a12efd22177616a1a0327335121b63f0d91f175b3344" - execution_block_height: 506 -- deposit_data: - pubkey: "0x8a6d7746a13800bc06e455dc52786c35065e2b08054ab8e569e28391dbef3d2527eea72aa60e1b53a54dc477055bccf2" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x96725d9c529a3bec9780a7d693e589c2e1b8e2d328d21e52a9ade535810501c0f2f9e45022564b3e8c9c371d9cfada1e03098990f36a9a2e6defd299ce06e9ff8f90a683a2a4bc0083bb2c424a4e3311f95cbd030962305808dfafd5967eb1ba" - deposit_data_root: "0x998a3d35a682a46fde29ea288109b96a4b24a246a7d4b24f7572bcff109d6ade" - eth1_data: - deposit_root: "0xc748ed0905ebcfec8085a8c67091ac467642915c73dd758f432b5e8658a90044" - deposit_count: "506" - block_hash: "0x28b47914e0960b493976bc8ee4cc861fa19f640d6a3369df08b413c550540df9" - block_height: 507 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - - "0x9e659e07ef110f0141fdce5a789ae1a54952cadb33028e879430eb600ef4d58a" - deposit_root: "0xc748ed0905ebcfec8085a8c67091ac467642915c73dd758f432b5e8658a90044" - deposit_count: 506 - execution_block_hash: "0x28b47914e0960b493976bc8ee4cc861fa19f640d6a3369df08b413c550540df9" - execution_block_height: 507 -- deposit_data: - pubkey: "0x961cc399896c8daecb0784f91943dd32077c9ad661f460f27f048ffc557010b4bd9a4a692306b6f70ff0f597fd31ecbe" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x9049a268bf99261658ce1087a0b7874a059b951c7a03bc56f46ab0474efefc1c245bbda655bd994d25576df5ce7ef3e30345a606af54bf4d171dc0dddcf6122f71ec286f57a543767b2869ff2f84df30e324a682011c250e52d2bda21eef798d" - deposit_data_root: "0xc0107b388528dd8ec080388c8313dd88c9c6e6fc742a27081881a5209c084295" - eth1_data: - deposit_root: "0x0c016b6f117e5507d6e740412dda9f7e5cdd539650a8a424e7a06c836408078a" - deposit_count: "507" - block_hash: "0xf89a37f7162803e84f7ffbaf0f5ea73306993adfd524d7aa060d49316b96c607" - block_height: 508 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - - "0x9e659e07ef110f0141fdce5a789ae1a54952cadb33028e879430eb600ef4d58a" - - "0xc0107b388528dd8ec080388c8313dd88c9c6e6fc742a27081881a5209c084295" - deposit_root: "0x0c016b6f117e5507d6e740412dda9f7e5cdd539650a8a424e7a06c836408078a" - deposit_count: 507 - execution_block_hash: "0xf89a37f7162803e84f7ffbaf0f5ea73306993adfd524d7aa060d49316b96c607" - execution_block_height: 508 -- deposit_data: - pubkey: "0x8edb799cc0407cf8db901082d7fcd613b7dd4ea03caef14073841e54531bdcdf3f661d551edae045b960b506e70b8dc8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xa88cca9dc14b177d45e96da17be05c999f86f12db33ad5060b82e53f9a94949542bcec1ca0422d16de2439ee5fa1215615d16bd0e50ea1e3f9abcbe8096ca51837243a709a6fb02c9dfe443dc2e175af845465108d49132b0e94a3833e10080d" - deposit_data_root: "0xdc384b4f094fc826477b19d9126ec7e2937fc95fb19397073f00e211e9dfc8b1" - eth1_data: - deposit_root: "0xf0ac44f68f2ebad7f114356cabeee522a8dfd5713da8a875d28630edbf2ba3d6" - deposit_count: "508" - block_hash: "0x14ce1569b047c9a4015e7ff6b15b90cf19bae216e88e578651af70927afc5425" - block_height: 509 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - - "0x755680bc147f24f2b5988a005d2af577aaa2f9364a17d97436a7296698418aa3" - deposit_root: "0xf0ac44f68f2ebad7f114356cabeee522a8dfd5713da8a875d28630edbf2ba3d6" - deposit_count: 508 - execution_block_hash: "0x14ce1569b047c9a4015e7ff6b15b90cf19bae216e88e578651af70927afc5425" - execution_block_height: 509 -- deposit_data: - pubkey: "0x8eab84a4394a69ff45dad4a17d4a8ce3bb04948c68bc70abbdebdd76c2156507a319d5bf9cfcaf750fe3b4d5c66723ee" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x921e0672eee586061a353cb40a0586354fbcf92c7c94be47a0e35f58c183bac72959dac50d83cc6e826ae29540ae4924048bfa5adc5b6be5fc4df54816a41fc265f514e2393796c454e822baff3ecedfcff8c36ca56731df32ca9f868219db1a" - deposit_data_root: "0x9538b1a35a2401f1ae3146672fb986168c4872d6ce0ff63f9fd8fcc590b81a05" - eth1_data: - deposit_root: "0x2be7501704a28527b0cde140e090ae14b1951facdd019b1bbdb5a53165663b90" - deposit_count: "509" - block_hash: "0xc092fd8e7712ceb3e8b1db31d939638233c0768396159d836fd8fae04a020db3" - block_height: 510 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - - "0x755680bc147f24f2b5988a005d2af577aaa2f9364a17d97436a7296698418aa3" - - "0x9538b1a35a2401f1ae3146672fb986168c4872d6ce0ff63f9fd8fcc590b81a05" - deposit_root: "0x2be7501704a28527b0cde140e090ae14b1951facdd019b1bbdb5a53165663b90" - deposit_count: 509 - execution_block_hash: "0xc092fd8e7712ceb3e8b1db31d939638233c0768396159d836fd8fae04a020db3" - execution_block_height: 510 -- deposit_data: - pubkey: "0xb4c37ed40d2d3d2ace8ba3e1d0bb1b0651d3d21c3dc5e8a4ef42513d649fc14d6dcd1ba7c8b893a2205f6afc870feb75" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x990065021cd5c4395a74ee4ef2bd3ae57e6a69afbb520c9af1793e2272ccf96252cb24cdc06f566d930939e709493e700610c5de71a888a01ae07f6e9eae7995e8c4a8e2ae2a0c9d19832ebd90dc3218857253a453f419681fe5974a56f5beee" - deposit_data_root: "0x79922ab72714b5a5f07b6d4ad531260bdc3da6b2723f5a621a19f49d14f080d4" - eth1_data: - deposit_root: "0x904cf102790d85df5521caba1f278f99ed62a6b72fc1aa7a9c9731014b4b08c7" - deposit_count: "510" - block_hash: "0x59ecebf2c2f7d2de32d3de30320e7b278ca84c9ab21fecb0bc129a56eab60ef0" - block_height: 511 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - - "0x755680bc147f24f2b5988a005d2af577aaa2f9364a17d97436a7296698418aa3" - - "0x1ae3fbc472814f00a5434bbc76e084c37564a55fee67c206c9977c152b12c38e" - deposit_root: "0x904cf102790d85df5521caba1f278f99ed62a6b72fc1aa7a9c9731014b4b08c7" - deposit_count: 510 - execution_block_hash: "0x59ecebf2c2f7d2de32d3de30320e7b278ca84c9ab21fecb0bc129a56eab60ef0" - execution_block_height: 511 -- deposit_data: - pubkey: "0xb02b650ec8c8a75cdb3c8cc64b612c5180aa31e0345ce16b1d9d8e5c7ce28d4feb8ed6bab0be68c13a5acd71f326fab8" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0x8eb8590912f2736f59f2fc221f167f9806159c922a041d94e1cb7dbd1703e84d7ea54623ffd01aeefab48ded3accac940610dacc940565a4bdadbd586bb7ea7e95cd8a6439363963618fb3332ae2eb1a33fdde5a20871550188a5a21657b141b" - deposit_data_root: "0xaa58eeff0a6439e85c0062387253ed726b0dcb040f1ad6f9e2dd2ea9ef6b0d41" - eth1_data: - deposit_root: "0xdf4c6590595af97badd52a23a3d2da9e1171340d3e9d299ea462b482822e3680" - deposit_count: "511" - block_hash: "0xc5db6286114149aacff6eaae584d859fbbcbb914cb3f111447844e76e7623822" - block_height: 512 - snapshot: - finalized: - - "0x1be35e1ed8992a8d55a57ee0a215ffa733e7e66282acfd151ad96e813963f4a1" - - "0xce176f39f29e8b5f6cbd3d107bf8a2e6162266aef4ee4aa784e6c899474ca670" - - "0xbfa916399e17a4550bfb5997c9b20433cc80c876ad0cf18df57493bce4dc8d29" - - "0x61f220b3f77327db30183075bd0e691acebd9aa729ace9b85d6381f40bbabd15" - - "0xbe91342b23cac4f3b516349701aa3357b170da8ebd6769691d1c633f0391b2be" - - "0xe129a1eb08eea7dc270c6d43e68441450375894e2bb046328e0ce35a68faca31" - - "0x755680bc147f24f2b5988a005d2af577aaa2f9364a17d97436a7296698418aa3" - - "0x1ae3fbc472814f00a5434bbc76e084c37564a55fee67c206c9977c152b12c38e" - - "0xaa58eeff0a6439e85c0062387253ed726b0dcb040f1ad6f9e2dd2ea9ef6b0d41" - deposit_root: "0xdf4c6590595af97badd52a23a3d2da9e1171340d3e9d299ea462b482822e3680" - deposit_count: 511 - execution_block_hash: "0xc5db6286114149aacff6eaae584d859fbbcbb914cb3f111447844e76e7623822" - execution_block_height: 512 -- deposit_data: - pubkey: "0x890bbabc36dc93557dffada73af2e71bd15481fb5345d7b4755498ddb1bd8733f0140cc40007c76ccc8764ba42678d1e" - withdrawal_credentials: "0x0000000000000000000000000000000000000000000000000000000000000000" - amount: "32000000000" - signature: "0xb2e4b4c3d177fbeacd0a4cfbb4eb8504e380b2354b5ffac72705ff61fbea065af6793ec133971dcc7da8cef0fe3ff61d06ea05be4f1e2b88c29be2d769008a9a1385c156861553587913c0a0fe74fc3a2efdc90111a0a1fbc96f7c16234c0909" - deposit_data_root: "0xa26a328f61bac695e879575ad50b38d5e643ff8df03bfd1cd7f55da6e64e9228" - eth1_data: - deposit_root: "0x556a4bfa525440a9e36ac1758db883caf80a0226ea195e91445e01621a67f875" - deposit_count: "512" - block_hash: "0x5d025ae62b16de0ebfc98e502cc0aa0e583efa4537d6c68273634ebbcb648749" - block_height: 513 - snapshot: - finalized: - - "0xff99e90235f5818855c9114be9ce29d0f028bcab5032cfbcf634609b211268b1" - deposit_root: "0x556a4bfa525440a9e36ac1758db883caf80a0226ea195e91445e01621a67f875" - deposit_count: 512 - execution_block_hash: "0x5d025ae62b16de0ebfc98e502cc0aa0e583efa4537d6c68273634ebbcb648749" - execution_block_height: 513 - diff --git a/assets/eip-4881/test_deposit_snapshot.py b/assets/eip-4881/test_deposit_snapshot.py deleted file mode 100755 index c0b8ed1..0000000 --- a/assets/eip-4881/test_deposit_snapshot.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import yaml -from dataclasses import dataclass -from deposit_snapshot import DepositTree,DepositTreeSnapshot -from eip_4881 import DepositData,DEPOSIT_CONTRACT_DEPTH,Eth1Data,Hash32,sha256,uint64,zerohashes - -@dataclass -class DepositTestCase: - deposit_data: DepositData - deposit_data_root: Hash32 - eth1_data: Eth1Data - block_height: uint64 - snapshot: DepositTreeSnapshot - -def get_hex(some_bytes) -> str: - return "0x{}".format(some_bytes.hex()) - -def get_bytes(hexstr) -> bytes: - return bytes.fromhex(hexstr.replace("0x","")) - -def read_test_cases(filename): - with open(filename, "r") as file: - try: - test_cases = yaml.safe_load(file) - result = [] - for test_case in test_cases: - deposit_data = DepositData( - get_bytes(test_case['deposit_data']['pubkey']), - get_bytes(test_case['deposit_data']['withdrawal_credentials']), - int(test_case['deposit_data']['amount']), - get_bytes(test_case['deposit_data']['signature']) - ) - eth1_data = Eth1Data( - get_bytes(test_case['eth1_data']['deposit_root']), - int(test_case['eth1_data']['deposit_count']), - get_bytes(test_case['eth1_data']['block_hash']) - ) - finalized = [] - for block_hash in test_case['snapshot']['finalized']: - finalized.append(get_bytes(block_hash)) - snapshot = DepositTreeSnapshot( - finalized, - get_bytes(test_case['snapshot']['deposit_root']), - int(test_case['snapshot']['deposit_count']), - get_bytes(test_case['snapshot']['execution_block_hash']), - int(test_case['snapshot']['execution_block_height']) - ) - result.append(DepositTestCase( - deposit_data, - get_bytes(test_case['deposit_data_root']), - eth1_data, - int(test_case['block_height']), - snapshot - )) - return result - except yaml.YAMLError as exc: - print(exc) - assert(False) - -def merkle_root_from_branch(leaf, branch, index) -> Hash32: - root = leaf - for (i, leaf) in enumerate(branch): - ith_bit = (index >> i) & 0x1 - if ith_bit == 1: - root = sha256(leaf + root) - else: - root = sha256(root + leaf) - return root - -def check_proof(tree, index): - leaf, proof = tree.get_proof(index) - calc_root = merkle_root_from_branch(leaf, proof, index) - assert(calc_root == tree.get_root()) - -def compare_proof(tree1, tree2, index): - assert(tree1.get_root() == tree2.get_root()) - check_proof(tree1, index) - check_proof(tree2, index) - -def clone_from_snapshot(snapshot, test_cases): - copy = DepositTree.from_snapshot(snapshot) - for case in test_cases: - copy.push_leaf(case.deposit_data_root) - return copy - -def test_instantiate(): - DepositTree.new() - -def test_empty_root(): - empty = DepositTree.new() - assert( - empty.get_root() == - bytes.fromhex( - "d70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e" - ) - ) - -def test_deposit_cases(): - tree = DepositTree.new() - test_cases = read_test_cases("test_cases.yaml") - for case in test_cases: - tree.push_leaf(case.deposit_data_root) - expected = case.eth1_data.deposit_root - assert(case.snapshot.calculate_root() == expected) - assert(tree.get_root() == expected) - -def test_finalization(): - tree = DepositTree.new() - test_cases = read_test_cases("test_cases.yaml")[:128] # only need subset - for case in test_cases: - tree.push_leaf(case.deposit_data_root) - original_root = tree.get_root() - assert(original_root == test_cases[127].eth1_data.deposit_root) - tree.finalize(test_cases[100].eth1_data, test_cases[100].block_height) - # ensure finalization doesn't change root - assert(tree.get_root() == original_root) - snapshot = tree.get_snapshot() - assert(snapshot == test_cases[100].snapshot) - # create a copy of the tree from a snapshot by replaying - # the deposits after the finalized deposit - copy = clone_from_snapshot(snapshot, test_cases[101:128]) - # ensure original and copy have the same root - assert(tree.get_root() == copy.get_root()) - # finalize original again to check double finalization - tree.finalize(test_cases[105].eth1_data, test_cases[105].block_height) - # root should still be the same - assert(tree.get_root() == original_root) - # create a copy of the tree by taking a snapshot again - copy = clone_from_snapshot(tree.get_snapshot(), test_cases[106:128]) - # create a copy of the tree by replaying ALL deposits from nothing - full_tree_copy = DepositTree.new() - for case in test_cases: - full_tree_copy.push_leaf(case.deposit_data_root) - # ensure the proofs are the same and valid for each tree - for index in range(106, 128): - compare_proof(tree, copy, index) - compare_proof(tree, full_tree_copy, index) - -def test_snapshot_cases(): - tree = DepositTree.new() - test_cases = read_test_cases("test_cases.yaml") - for case in test_cases: - tree.push_leaf(case.deposit_data_root) - - for case in test_cases: - tree.finalize(case.eth1_data, case.block_height) - assert(tree.get_snapshot() == case.snapshot) - -def test_empty_tree_snapshot(): - with pytest.raises(AssertionError): - # can't get snapshot from tree that hasn't been finalized - snapshot = DepositTree.new().get_snapshot() - -def test_invalid_snapshot(): - with pytest.raises(AssertionError): - # invalid snapshot (deposit root doesn't match) - invalid_snapshot = DepositTreeSnapshot([], zerohashes[0], 0, zerohashes[0], 0) - tree = DepositTree.from_snapshot(invalid_snapshot) - diff --git a/assets/eip-4886/contracts/EPS.sol b/assets/eip-4886/contracts/EPS.sol deleted file mode 100644 index 303450c..0000000 --- a/assets/eip-4886/contracts/EPS.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.7.0 (epsproxy/contracts/EPS.sol) - -pragma solidity ^0.8.9; - -/** - * @dev Implementation of the EPS register interface. - */ -interface EPS { - // Emitted when an address nominates a proxy address: - event NominationMade(address indexed nominator, address indexed proxy, uint256 timestamp, uint256 provider); - // Emitted when an address accepts a proxy nomination: - event NominationAccepted(address indexed nominator, address indexed proxy, address indexed delivery, uint256 timestamp, uint256 provider); - // Emitted when the proxy address updates the delivery address on a record: - event DeliveryUpdated(address indexed nominator, address indexed proxy, address indexed delivery, address oldDelivery, uint256 timestamp, uint256 provider); - // Emitted when a nomination record is deleted. initiator 0 = nominator, 1 = proxy: - event NominationDeleted(string initiator, address indexed nominator, address indexed proxy, uint256 timestamp, uint256 provider); - // Emitted when a register record is deleted. initiator 0 = nominator, 1 = proxy: - event RecordDeleted(string initiator, address indexed nominator, address indexed proxy, address indexed delivery, uint256 timestamp, uint256 provider); - // Emitted when the register fee is set: - event RegisterFeeSet(uint256 indexed registerFee); - // Emitted when the treasury address is set: - event TreasuryAddressSet(address indexed treasuryAddress); - // Emitted on withdrawal to the treasury address: - event Withdrawal(uint256 indexed amount, uint256 timestamp); - - function nominationExists(address _nominator) external view returns (bool); - function nominationExistsForCaller() external view returns (bool); - function proxyRecordExists(address _proxy) external view returns (bool); - function proxyRecordExistsForCaller() external view returns (bool); - function nominatorRecordExists(address _nominator) external view returns (bool); - function nominatorRecordExistsForCaller() external view returns (bool); - function getProxyRecord(address _proxy) external view returns (address nominator, address proxy, address delivery); - function getProxyRecordForCaller() external view returns (address nominator, address proxy, address delivery); - function getNominatorRecord(address _nominator) external view returns (address nominator, address proxy, address delivery); - function getNominatorRecordForCaller() external view returns (address nominator, address proxy, address delivery); - function addressIsActive(address _receivedAddress) external view returns (bool); - function addressIsActiveForCaller() external view returns (bool); - function getNomination(address _nominator) external view returns (address proxy); - function getNominationForCaller() external view returns (address proxy); - function getAddresses(address _receivedAddress) external view returns (address nominator, address delivery, bool isProxied); - function getAddressesForCaller() external view returns (address nominator, address delivery, bool isProxied); - function getRole(address _roleAddress) external view returns (string memory currentRole); - function getRoleForCaller() external view returns (string memory currentRole); - function makeNomination(address _proxy, uint256 _provider) external payable; - function acceptNomination(address _nominator, address _delivery, uint256 _provider) external; - function updateDeliveryAddress(address _delivery, uint256 _provider) external; - function deleteRecordByNominator(uint256 _provider) external; - function deleteRecordByProxy(uint256 _provider) external; - function setRegisterFee(uint256 _registerFee) external returns (bool); - function getRegisterFee() external view returns (uint256 _registerFee); - function setTreasuryAddress(address _treasuryAddress) external returns (bool); - function getTreasuryAddress() external view returns (address _treasuryAddress); - function withdraw(uint256 _amount) external returns (bool); -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/Proxiable.sol b/assets/eip-4886/contracts/Proxiable.sol deleted file mode 100644 index c6607b6..0000000 --- a/assets/eip-4886/contracts/Proxiable.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.7.0 (epsproxy/contracts/Proxiable.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@epsproxy/contracts/EPS.sol"; - -/** - * @dev Contract module which allows children to implement calls to the EPS - * proxy registry - */ - -abstract contract Proxiable is Context { - - EPS eps; // Address for the relevant chain passed in on the constructor. - - /** - * @dev Constructor initialises the register contract object - */ - constructor( - address _epsRegisterAddress - ) { - eps = EPS(_epsRegisterAddress); - } - - /** - * @dev Returns the proxied address details (nominator and delivery address) for a passed proxy address - */ - function getAddresses(address _receivedAddress) internal view returns (address nominator, address delivery, bool isProxied) { - return (eps.getAddresses(_receivedAddress)); - } - - /** - * @dev Returns true if this is currently a proxy address (i.e. has an entry that isn't expired): - */ - function proxyRecordExists(address _receivedAddress) internal view returns (bool isProxied) { - return (eps.proxyRecordExists(_receivedAddress)); - } - - /** - * @dev Returns an ERC20 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists. - */ - function ERC20BalanceOfNominator(address _receivedAddress, address tokenContract) internal virtual view returns (uint256 _tokenBalance){ - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - return IERC20(tokenContract).balanceOf(nominator); - } - - /** - * @dev Returns an ERC20 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists, IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to proxyRecordExists that determines if a proxy address is in use, which is then passed in on the call - * to the contract inheriting this method. This saves gas for anyone who is NOT using a proxy as we do not needlessly check for proxy details. - */ - function ERC20BalanceOfNominatorSwitched(address _receivedAddress, address _tokenContract, bool _isProxied) internal virtual view returns (uint256 _tokenBalance){ - if (_isProxied) { - return ERC20BalanceOfNominator(_receivedAddress, _tokenContract); - } - else { - return IERC20(_tokenContract).balanceOf(_receivedAddress); - } - } - - /** - * @dev Returns an ERC721 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists. - */ - function ERC721BalanceOfNominator(address _proxy, address tokenContract) internal virtual view returns (uint256 _tokenBalance){ - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_proxy); - return IERC721(tokenContract).balanceOf(nominator); - } - - /** - * @dev Returns an ERC721 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists, IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to proxyRecordExists that determines if a proxy address is in use, which is then passed in on the call - * to the contract inheriting this method . This saves gas for anyone who is NOT using a proxy as we do not needlessly check for proxy details. - */ - function ERC721BalanceOfNominatorSwitched(address _receivedAddress, address _tokenContract, bool _isProxied) internal virtual view returns (uint256 _tokenBalance){ - if (_isProxied) { - return ERC721BalanceOfNominator(_receivedAddress, _tokenContract); - } - else { - return IERC721(_tokenContract).balanceOf(_receivedAddress); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/ProxyRegister.sol b/assets/eip-4886/contracts/ProxyRegister.sol deleted file mode 100644 index cf84817..0000000 --- a/assets/eip-4886/contracts/ProxyRegister.sol +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.7.0 (epsproxy/contracts/ProxyRegister.sol) - -pragma solidity ^0.8.9; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@epsproxy/contracts/EPS.sol"; - -/** - * @dev The EPS Register contract. - */ -contract ProxyRegister is EPS, Ownable { - - struct Record { - address nominator; - address delivery; - } - - uint256 private registerFee; - address private treasury; - - mapping (address => address) nominatorToProxy; - mapping (address => Record) proxyToRecord; - - /** - * @dev Constructor initialises the register fee and treasury address: - */ - constructor( - uint256 _registerFee, - address _treasury - ) { - setRegisterFee(_registerFee); - setTreasuryAddress(_treasury); - } - - /** - * @dev Nominators can nominate ONCE only - */ - modifier isNotCurrentNominator(address _nominator) { - require(!nominationExists(_nominator), "Address has an existing nomination"); - _; - } - - /** - * @dev Check if this nominator is already on the registry - */ - modifier isExistingNominator(address _nominator) { - require(nominationExists(_nominator), "Nominator entry does not exist"); - _; - } - - /** - * @dev Proxys can act as proxy ONCE only - */ - modifier isNotCurrentProxy(address _proxy) { - require(!proxyRecordExists(_proxy), "Address is already acting as a proxy"); - _; - } - - /** - * @dev Check if this proxy is already on the registry - */ - modifier isExistingProxy(address _proxy) { - require(proxyRecordExists(_proxy), "Proxy entry does not exist"); - _; - } - - /** - * @dev Return if an entry exists for this nominator address - */ - function nominationExists(address _nominator) public view returns (bool) { - return nominatorToProxy[_nominator] != address(0); - } - - /** - * @dev Return if an entry exists for this nominator address - For Caller - */ - function nominationExistsForCaller() public view returns (bool) { - return nominationExists(msg.sender); - } - - /** - * @dev Return if an entry exists for this proxy address - */ - function proxyRecordExists(address _proxy) public view returns (bool) { - return proxyToRecord[_proxy].nominator != address(0); - } - - /** - * @dev Return if an entry exists for this proxy address - For Caller - */ - function proxyRecordExistsForCaller() external view returns (bool) { - return proxyRecordExists(msg.sender); - } - - /** - * @dev Return if an entry exists for this nominator address - */ - function nominatorRecordExists(address _nominator) public view returns (bool) { - return proxyToRecord[nominatorToProxy[_nominator]].nominator != address(0); - } - - /** - * @dev Return if an entry exists for this nominator address - For Caller - */ - function nominatorRecordExistsForCaller() external view returns (bool) { - return nominatorRecordExists(msg.sender); - } - - /** - * @dev Return if the address is an active proxy address OR a nominator with an active proxy - */ - function addressIsActive(address _receivedAddress) public view returns (bool) { - bool isActive = false; - // Check if the address is an active proxy address or active nominator: - if (proxyRecordExists(_receivedAddress) || nominatorRecordExists(_receivedAddress)){ - isActive = true; - } - return isActive; - } - - /** - * @dev Return if the address is an active proxy address OR a nominator with an active proxy - For Caller - */ - function addressIsActiveForCaller() external view returns (bool) { - return addressIsActive(msg.sender); - } - - /** - * @dev Get entry details by proxy - */ - function getProxyRecord(address _proxy) public view returns (address nominator, address proxy, address delivery) { - Record memory currentItem = proxyToRecord[_proxy]; - return (currentItem.nominator, nominatorToProxy[currentItem.nominator], currentItem.delivery); - } - - /** - * @dev Get entry details by proxy - For Caller - */ - function getProxyRecordForCaller() external view returns (address nominator, address proxy, address delivery) { - return (getProxyRecord(msg.sender)); - } - - /** - * @dev Get entry details by nominator - */ - function getNominatorRecord(address _nominator) public view returns (address nominator, address proxy, address delivery) { - address proxyAddress = nominatorToProxy[_nominator]; - if (proxyToRecord[proxyAddress].nominator == address(0)) { - // This function returns registry entries. If there is no entry on the registry (despite there being a nomination), do - // not return the proxy address: - proxyAddress = address(0); - } - return (proxyToRecord[proxyAddress].nominator, proxyAddress, proxyToRecord[proxyAddress].delivery); - } - - /** - * @dev Get entry details by nominator - For Caller - */ - function getNominatorRecordForCaller() external view returns (address nominator, address proxy, address delivery) { - return (getNominatorRecord(msg.sender)); - } - - /** - * @dev Get nomination details only for nominator - */ - function getNomination(address _nominator) public view returns (address proxy) { - return (nominatorToProxy[_nominator]); - } - - /** - * @dev Get nomination details only for nominator - For Caller - */ - function getNominationForCaller() public view returns (address proxy) { - return (getNomination(msg.sender)); - } - - /** - * @dev Returns the proxied address details (nominator and delivery address) for a passed proxy address - */ - function getAddresses(address _receivedAddress) public view returns (address nominator, address delivery, bool isProxied) { - require(!nominationExists(_receivedAddress), "Nominator address cannot interact directly, only through the proxy address"); - Record memory currentItem = proxyToRecord[_receivedAddress]; - if (proxyToRecord[_receivedAddress].nominator == address(0)) { - return(_receivedAddress, _receivedAddress, false); - } - else { - return (currentItem.nominator, currentItem.delivery, true); - } - } - - /** - * @dev Returns the proxied address details (owner and delivery address) for the msg.sender being interacted with - */ - function getAddressesForCaller() external view returns (address nominator, address delivery, bool isProxied) { - return (getAddresses(msg.sender)); - } - - /** - * @dev Returns the current role of a given address (nominator, proxy, none) - */ - function getRole(address _roleAddress) public view returns (string memory currentRole) { - if (proxyRecordExists(_roleAddress)) { - return "Proxy"; - } - if (nominationExists(_roleAddress)) { - if (proxyRecordExists(nominatorToProxy[_roleAddress])) { - return "Nominator - Proxy Active"; - } - else { - return "Nominator - Proxy Pending"; - } - } - return "None"; - } - - /** - * @dev Returns the current role of a given address (nominator, proxy, none) - For Caller - */ - function getRoleForCaller() external view returns (string memory currentRole) { - return getRole(msg.sender); - } - - /** - * @dev The nominator initiates a proxy entry - */ - function makeNomination(address _proxy, uint256 _provider) external payable isNotCurrentNominator(msg.sender) isNotCurrentProxy(_proxy) isNotCurrentProxy(msg.sender) { - require (_proxy != address(0), "Proxy address must be provided"); - require (_proxy != msg.sender, "Proxy address cannot be the same as Nominator address"); - require(msg.value == registerFee, "Register fee must be paid"); - nominatorToProxy[msg.sender] = _proxy; - emit NominationMade(msg.sender, _proxy, block.timestamp, _provider); - } - - /** - * @dev Proxy accepts nomination - */ - function acceptNomination(address _nominator, address _delivery, uint256 _provider) external isNotCurrentProxy(msg.sender) isNotCurrentProxy(_nominator) { - // The nominator must be passed in: - require (_nominator != address(0), "Nominator address must be provided"); - // The sender must match the proxy nomination: - require (nominatorToProxy[_nominator] == msg.sender, "Caller is not the nominated proxy for this nominator"); - // We have a valid nomination, create the ProxyRegisterItem: - proxyToRecord[msg.sender] = Record(_nominator, _delivery); - emit NominationAccepted(_nominator, msg.sender, _delivery, block.timestamp, _provider); - } - - /** - * @dev Change delivery address on an existing proxy item. Can only be called by the proxy address. - */ - function updateDeliveryAddress(address _delivery, uint256 _provider) external isExistingProxy(msg.sender) { - Record memory priorItem = proxyToRecord[msg.sender]; - proxyToRecord[msg.sender].delivery = _delivery; - emit DeliveryUpdated(priorItem.nominator, msg.sender, _delivery, priorItem.delivery, block.timestamp, _provider); - } - - /** - * @dev delete a proxy entry. BOTH the nominator and proxy can delete a proxy arrangement and all - * aspects of that proxy arrangement will be removed. - */ - function deleteRecordByNominator(uint256 _provider) external isExistingNominator(msg.sender) { - deleteProxyRegisterItems(msg.sender, nominatorToProxy[msg.sender], "nominator", _provider); - } - - /** - * @dev delete a proxy entry. BOTH the nominator and proxy can delete a proxy arrangement and all - * aspects of that proxy arrangement will be removed. - */ - function deleteRecordByProxy(uint256 _provider) external isExistingProxy(msg.sender) { - deleteProxyRegisterItems(proxyToRecord[msg.sender].nominator, msg.sender, "proxy", _provider); - } - - /** - * @dev delete the nomination and record (if present) - */ - function deleteProxyRegisterItems(address _nominator, address _proxy, string memory _initiator, uint256 _provider) internal { - // First remove the nomination. We know this must exists, as it has to come before the proxy can be accepted: - delete nominatorToProxy[_nominator]; - emit NominationDeleted(_initiator, _nominator, _proxy, block.timestamp, _provider); - // Now remove the proxy register item. If the nominator is deleting a nomination that has not been accepted by a proxy - // then this will not exists. Check that the proxy is for this nominator. - if (proxyToRecord[_proxy].nominator == _nominator) { - address deletedDelivery = proxyToRecord[_proxy].delivery; - delete proxyToRecord[_proxy]; - emit RecordDeleted(_initiator, _nominator, _proxy, deletedDelivery, block.timestamp, _provider); - } - } - - /** - * @dev set the fee for initiating a registration (accepting a proxy, updating the delivery address and deletions will always be free) - */ - function setRegisterFee(uint256 _registerFee) public onlyOwner returns (bool) - { - require(_registerFee != registerFee, "No change to register fee"); - registerFee = _registerFee; - emit RegisterFeeSet(registerFee); - return true; - } - - /** - * @dev return the register fee: - */ - function getRegisterFee() external view returns (uint256 _registerFee) { - return(registerFee); - } - - /** - * @dev set the treasury address: - */ - function setTreasuryAddress(address _treasuryAddress) public onlyOwner returns (bool) - { - require(_treasuryAddress != treasury, "No change to treasury address"); - treasury = _treasuryAddress; - emit TreasuryAddressSet(treasury); - return true; - } - - /** - * @dev get the treasury address: - */ - function getTreasuryAddress() external view returns (address _treasuryAddress) { - return(treasury); - } - - /** - * @dev withdraw eth to the treasury: - */ - function withdraw(uint256 _amount) external onlyOwner returns (bool) { - (bool success, ) = treasury.call{value: _amount}(""); - require(success, "Withdrawal failed."); - emit Withdrawal(_amount, block.timestamp); - return true; - } - - /** - * @dev revert fallback - */ - fallback() external payable { - revert(); - } - - /** - * @dev revert receive - */ - receive() external payable { - revert(); - } -} diff --git a/assets/eip-4886/contracts/examples/EPSExample721.sol b/assets/eip-4886/contracts/examples/EPSExample721.sol deleted file mode 100644 index b603ff2..0000000 --- a/assets/eip-4886/contracts/examples/EPSExample721.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/EPSExample721.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -contract EPSExample721 is ERC721, ERC721Burnable, Ownable, Proxiable { - using Counters for Counters.Counter; - using Strings for uint256; - - Counters.Counter private _tokenIdCounter; - bool hasMinted; - - mapping (address => bool) minterHasMinted; - uint256 constant MAX_SUPPLY = 10000; - - constructor(address _epsRegisterAddress) - ERC721("EPSExample721", "EPS721") - Proxiable(_epsRegisterAddress) { - } - - /** - * @dev This address hasn't minted already - */ - modifier hasNotAlreadyMinted(address _receivedAddress) { - require(minterHasMinted[_receivedAddress] != true, "Address has already minted, allocation exhausted"); - _; - } - - modifier isProxyAddress(address _receivedAddress) { - require(proxyRecordExists(_receivedAddress), "Only a proxy address can mint this token - go to app.epsproxy.com"); - _; - } - - modifier supplyNotExhausted() { - require(_tokenIdCounter.current() < MAX_SUPPLY, "Max supply reached - cannot be minted"); - _; - } - - function _baseURI() internal pure override returns (string memory) { - return "https://epsproxy.com/test/"; - } - - function totalSupply() public pure returns (uint256) { - return (MAX_SUPPLY); - } - - function tokenURI(uint256 tokenId) public view override(ERC721) returns (string memory) - { - require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - - string memory baseURI = _baseURI(); - return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) : ""; - } - - function proxyMint() external hasNotAlreadyMinted(msg.sender) isProxyAddress(msg.sender) supplyNotExhausted() { - uint256 tokenId = _tokenIdCounter.current(); - _tokenIdCounter.increment(); - _safeMintProxied(msg.sender, tokenId); - minterHasMinted[msg.sender] = true; - } - - function _burn(uint256 tokenId) internal override(ERC721) { - super._burn(tokenId); - } - - /** - * @dev call safemint after determining the delivery address. - */ - function _safeMintProxied(address _to, uint256 _tokenId) internal virtual { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_to); - _safeMint(delivery, _tokenId); - } - - /** - * @dev call safemint after determining the delivery address IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to _proxyRecordExists that determines if a proxy address is in use. This saves gas for anyone who is - * NOT using a proxy as we do not needlessly check for proxy details. - */ - function safeMintProxiedSwitch(address _to, uint256 _tokenId, bool _isProxied) internal virtual { - if (_isProxied) { - _safeMintProxied(_to, _tokenId); - } - else { - _safeMint(_to, _tokenId); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/examples/EPSGenesis.sol b/assets/eip-4886/contracts/examples/EPSGenesis.sol deleted file mode 100644 index f3eab57..0000000 --- a/assets/eip-4886/contracts/examples/EPSGenesis.sol +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/EPSGenesis.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -/** -* @dev Contract instance for the EPS Genesis ERC-1155. This contract provides two token -* types designed to showcase the capabilities of the Eternal Proxy Service. -* - Open mint: Every proxy address can mint ONE of these tokens. Delivery will be to the -* delivery address. This demonstrates: -* (a) Retrieval of information and processing based on information in the EPS Register. -* (b) Delivery of new assets to the delivery address specified on the register. -* - Gated mint: Every proxy address that acts for a nominator one of three pre-determined -* NFTs can mint the 'gated' token. This will again be delivered to the delivery address -* specified on the EPS register. In addition this demonstrates: -* (c) Checking of the contract balance of the Nominator to determine eligibility. In -* this use of the EPS register risk to the eligibility asset is entirely eliminated -* as the contract interaction is with the proxy address, which does not hold the -* asset, not the nominator. -*/ -contract EPSGenesis is ERC1155, Ownable, ERC1155Burnable, ERC1155Supply, Proxiable { - -/** -* @dev Not required, but provided to give consistency with ERC-721 in terms of display -* in tools like etherscan: -*/ - string public constant NAME = "EPSGenesis"; - string public constant SYMBOL = "EPSGEN"; - - /** - * @dev Definition of token classes and max supply of each: - */ - uint256 public constant OPEN_TOKEN = 0; - uint256 public constant GATED_TOKEN = 1; - uint256 public constant MAX_SUPPLY_OPEN = 10000; - uint256 public constant MAX_SUPPLY_GATED = 10000; - - /** - * @dev tokens that need to be held to mint the gated tokens, provided on the constructor: - */ - address public immutable GATE_1; - address public immutable GATE_2; - address public immutable GATE_3; - - /** - * @dev each address is entitled to just ONE of each type. Each is free, except gas. - */ - mapping (address => bool) minterHasMintedOpen; - mapping (address => bool) minterHasMintedGated; - - constructor(address _epsRegisterAddress, address _GATE_1, address _GATE_2, address _GATE_3, string memory _contractURI) - ERC1155(_contractURI) - Proxiable(_epsRegisterAddress) { - GATE_1 = _GATE_1; - GATE_2 = _GATE_2; - GATE_3 = _GATE_3; - } - - /** - * @dev modifiers to control access based on previous minting: - */ - modifier hasNotAlreadyMintedOpen(address _receivedAddress) { - require(minterHasMintedOpen[_receivedAddress] != true, "Address has already minted in open mint, allocation exhausted"); - _; - } - - modifier hasNotAlreadyMintedGated(address _receivedAddress) { - require(minterHasMintedGated[_receivedAddress] != true, "Address has already minted in gated mint, allocation exhausted"); - _; - } - - /** - * @dev modifier to only allow minting from a proxy address: - */ - modifier isProxyAddress(address _receivedAddress) { - require(proxyRecordExists(_receivedAddress), "Only a proxy address can mint this token - go to app.epsproxy.com"); - _; - } - - /** - * @dev modifier to ensure supply(s) is not exhausted: - */ - modifier supplyNotExhaustedOpen() { - require(totalSupply(OPEN_TOKEN) < MAX_SUPPLY_OPEN, "Max supply reached for open mint - cannot be minted"); - _; - } - - modifier supplyNotExhaustedGated() { - require(totalSupply(GATED_TOKEN) < MAX_SUPPLY_GATED, "Max supply reached for gated mint - cannot be minted"); - _; - } - - /** - * @dev perform minting of the open tokens: - */ - function proxyMintOpen(address _receivedAddress) internal hasNotAlreadyMintedOpen(_receivedAddress) isProxyAddress(_receivedAddress) supplyNotExhaustedOpen() { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - - _mint(delivery, OPEN_TOKEN, 1, ""); - - minterHasMintedOpen[_receivedAddress] = true; - } - - /** - * @dev perform minting of the gated tokens: - */ - function proxyMintGated(address _receivedAddress) internal hasNotAlreadyMintedGated(_receivedAddress) isProxyAddress(_receivedAddress) supplyNotExhaustedGated() { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - - require((IERC721(GATE_1).balanceOf(nominator) >= 1 || IERC721(GATE_2).balanceOf(nominator) >= 1 || IERC721(GATE_3).balanceOf(nominator) >= 1), "Must hold an eligible token for this mint"); - - _mint(delivery, GATED_TOKEN, 1, ""); - - minterHasMintedGated[_receivedAddress] = true; - } - - /** - * @dev external function for minting calls. Required boolean values for open and gated minting (can pass both as true to perform both) - */ - function mintEPSGenesis(bool mintOpen, bool mintGated) external { - if (mintOpen) { - proxyMintOpen(msg.sender); - } - if (mintGated) { - proxyMintGated(msg.sender); - } - } - - /** - * @dev external and public functions to determine eligibility off-chain: - */ - function hasMintedOpen(address _receivedAddress) external view returns (bool) { - return(minterHasMintedOpen[_receivedAddress]); - } - - function hasMintedGated(address _receivedAddress) external view returns (bool) { - return(minterHasMintedGated[_receivedAddress]); - } - - function hasAProxyRecord(address _receivedAddress) external view returns (bool) { - return(proxyRecordExists(_receivedAddress)); - } - - function hasNominatorWithEligibleToken(address _receivedAddress) public view returns (bool) { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - return((IERC721(GATE_1).balanceOf(nominator) >= 1 || IERC721(GATE_2).balanceOf(nominator) >= 1 || IERC721(GATE_3).balanceOf(nominator) >= 1)); - } - - function addressStatus(address _receivedAddress) public view returns (bool open, bool gated, bool proxy, bool eligible) { - return(minterHasMintedOpen[_receivedAddress], minterHasMintedGated[_receivedAddress], proxyRecordExists(_receivedAddress), hasNominatorWithEligibleToken(_receivedAddress)); - } - - /** - * @dev name, max and total supply for tools like etherscan: - */ - function name() public pure returns (string memory) { - return NAME; - } - - function symbol() public pure returns (string memory) { - return SYMBOL; - } - - function totalSupply() public pure returns (uint256) { - return (MAX_SUPPLY_OPEN + MAX_SUPPLY_GATED); - } - - // The following functions are overrides required by Solidity. - - function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) - internal - override(ERC1155, ERC1155Supply) - { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/examples/ERC20Proxied.sol b/assets/eip-4886/contracts/examples/ERC20Proxied.sol deleted file mode 100644 index 9971c3f..0000000 --- a/assets/eip-4886/contracts/examples/ERC20Proxied.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/ERC20Proxied.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -/** - * @dev Contract module which allows children to implement proxied delivery - * on minting calls - */ -abstract contract ERC20Proxied is Context, ERC20, Proxiable { - - /** - * @dev call mint after determining the delivery address. - */ - function _mintProxied(address _account, uint256 _amount) internal virtual { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_account); - _mint(delivery, _amount); - } - - /** - * @dev call mint after determining the delivery address IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to _proxyRecordExists that determines if a proxy address is in use. This saves gas for anyone who is - * NOT using a proxy as we do not needlessly check for proxy details. - */ - function _MintProxiedSwitch(address _account, uint256 _amount, bool _isProxied) internal virtual { - if (_isProxied) { - _mintProxied(_account, _amount); - } - else { - _mint(_account, _amount); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/examples/ERC721Proxied.sol b/assets/eip-4886/contracts/examples/ERC721Proxied.sol deleted file mode 100644 index f0df21e..0000000 --- a/assets/eip-4886/contracts/examples/ERC721Proxied.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/ERC721Proxied.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -/** - * @dev Contract module which allows children to implement proxied delivery - * on minting calls - */ -abstract contract ERC721Proxied is Context, ERC721, Proxiable { - - constructor( - address _epsRegisterAddress, - string memory _name, - string memory _symbol - ) Proxiable(_epsRegisterAddress) - ERC721("_name", "_symbol") - { - } - - /** - * @dev Returns the proxied address details (nominator address, delivery address) for a passed proxy address. - * Call this to view the details for any given proxy address. - */ - function _getAddresses(address _receivedAddress) internal virtual view returns (address _nominator, address _delivery, bool _isProxied){ - return (getAddresses(_receivedAddress)); - } - - /** - * @dev Returns if a given address is a proxy or not: - */ - function _proxyRecordExists(address _receivedAddress) internal virtual view returns (bool _isProxied){ - return (proxyRecordExists(_receivedAddress)); - } - - /** - * @dev call safemint after determining the delivery address. - */ - function _safeMintProxied(address _to, uint256 _tokenId) internal virtual { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_to); - _safeMint(delivery, _tokenId); - } - - /** - * @dev call safemint after determining the delivery address IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to _proxyRecordExists that determines if a proxy address is in use. This saves gas for anyone who is - * NOT using a proxy as we do not needlessly check for proxy details. - */ - function _safeMintProxiedSwitch(address _to, uint256 _tokenId, bool _isProxied) internal virtual { - if (_isProxied) { - _safeMintProxied(_to, _tokenId); - } - else { - _safeMint(_to, _tokenId); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/test/ProxyRegister.js b/assets/eip-4886/test/ProxyRegister.js deleted file mode 100644 index 47da8a1..0000000 --- a/assets/eip-4886/test/ProxyRegister.js +++ /dev/null @@ -1,1412 +0,0 @@ -const { expect } = require("chai") -const registerFee = 0.005; -const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" -const tokenURI = "https://arweave.net/_Kk3lIGmZTnwT6Q67UOTlNG2biZq1F8F9jmoH3EaA14/{id}.json" - -describe("Genesis1155", function () { - let hardhatProxyRegister - let owner - let addr1 - let addr2 - let addr3 - let treasury - let addrs - - beforeEach(async function () { - ;[owner, addr1, addr2, addr3, treasury, ...addrs] = await ethers.getSigners() - - const ProxyRegister = await ethers.getContractFactory("ProxyRegister") - hardhatProxyRegister = await ProxyRegister.deploy( - ethers.utils.parseEther(registerFee.toString()), - treasury.address, - ) - - const mockERC721 = await ethers.getContractFactory("mockERC721") - hardhatBoredApes = await mockERC721.deploy() - hardhatLazyLions = await mockERC721.deploy() - hardhatLoomlockNFT = await mockERC721.deploy() - - const EPSGenOne = await ethers.getContractFactory("EPSGenesis") - hardhatEPSGenOne = await EPSGenOne.deploy( - hardhatProxyRegister.address, - hardhatBoredApes.address, - hardhatLazyLions.address, - hardhatLoomlockNFT.address, - tokenURI - ) - - }) - - context("Non-proxy addresses", function () { - describe("Minting", function () { - it("Cannot mint both NFTs", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - - }) - it("Cannot mint open NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, false), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - it("Cannot mint gated NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(false, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - }) - - }); - - context("Proxy address", function () { - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - }) - - describe("Nominator Minting", function () { - it("Cannot mint both NFTs", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - - }) - it("Cannot mint open NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, false), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - it("Cannot mint gated NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(false, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - }) - - describe("Proxy Minting - no eligible token", function () { - it("Cannot mint both NFTs", async () => { - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(true, true), - ).to.be.revertedWith("Must hold an eligible token for this mint") - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("Cannot mint gated NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(false, true), - ).to.be.revertedWith("Must hold an eligible token for this mint") - }) - }) - - describe("Proxy Minting - eligible token 1", function () { - beforeEach(async function () { - var tx1 = await hardhatBoredApes - .connect(addr1) - .safeMint() - - }) - - it("CAN mint both NFTs", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("CAN mint gated NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - }) - }) - - describe("Proxy Minting - eligible token 2", function () { - beforeEach(async function () { - var tx1 = await hardhatLazyLions - .connect(addr1) - .safeMint() - - }) - - it("CAN mint both NFTs", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("CAN mint gated NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - }) - }) - - describe("Proxy Minting - eligible token 3", function () { - beforeEach(async function () { - var tx1 = await hardhatLoomlockNFT - .connect(addr1) - .safeMint() - - }) - - it("CAN mint both NFTs", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("CAN mint gated NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - }) - }) - - describe("Proxy Minting - eligible token, cannot mint 2 of either", function () { - beforeEach(async function () { - var tx1 = await hardhatBoredApes - .connect(addr1) - .safeMint() - - }) - - it("Cannot mint 2 on call to both", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(true, true), - ).to.be.revertedWith("Address has already minted in open mint, allocation exhausted") - - }) - it("Cannot mint 2 open", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(true, false), - ).to.be.revertedWith("Address has already minted in open mint, allocation exhausted") - }) - it("Cannot mint 2 gated", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(false, true), - ).to.be.revertedWith("Address has already minted in gated mint, allocation exhausted") - }) - }) - }); -}); - -describe("ProxyRegister", function () { - let hardhatProxyRegister - let owner - let addr1 - let addr2 - let addr3 - let treasury - let addrs - - beforeEach(async function () { - ;[owner, addr1, addr2, addr3, treasury, ...addrs] = await ethers.getSigners() - - const ProxyRegister = await ethers.getContractFactory("ProxyRegister") - hardhatProxyRegister = await ProxyRegister.deploy( - ethers.utils.parseEther(registerFee.toString()), - treasury.address, - ) - }) - - context("Contract Setup", function () { - describe("Constructor", function () { - it("Has a contract balance of 0", async () => { - const contractBalance = await ethers.provider.getBalance( - hardhatProxyRegister.address, - ) - expect(contractBalance).to.equal(0) - }) - }) - }); - - context("Owner Only Functions", function () { - describe("Owner can execute", function () { - - it("Set registerFee", async () => { - var tx1 = await hardhatProxyRegister - .connect(owner) - .setRegisterFee(ethers.utils.parseEther("0.01")) - expect(tx1).to.emit(hardhatProxyRegister, "RegisterFeeSet") - var receipt = await tx1.wait() - expect(receipt.events[0].args.registerFee).to.equal(BigInt(ethers.utils.parseEther("0.01"))) - - const registerFeeParameter = await hardhatProxyRegister.getRegisterFee() - expect(registerFeeParameter).to.equal(ethers.utils.parseEther("0.01")) - }) - - it("Set treasuryAddress", async () => { - var tx1 = await hardhatProxyRegister - .connect(owner) - .setTreasuryAddress(addr3.address) - expect(tx1).to.emit(hardhatProxyRegister, "TreasuryAddressSet") - var receipt = await tx1.wait() - expect(receipt.events[0].args.treasuryAddress).to.equal(addr3.address) - - const treasuryAddressParameter = await hardhatProxyRegister.getTreasuryAddress() - expect(treasuryAddressParameter).to.equal(addr3.address) - }) - }) - - describe("Non-owner cannot execute", function () { - it("Set registerFee", async () => { - await expect( - hardhatProxyRegister.connect(addr1).setRegisterFee(ethers.utils.parseEther("0.01")), - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - - it("Set treasuryAddress", async () => { - await expect( - hardhatProxyRegister.connect(addr1).setTreasuryAddress(addr3.address), - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - }) - - describe("Withdraw", async () => { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - }) - - it("fails if not owner", async () => { - await expect( - hardhatProxyRegister.connect(addr1).withdraw(ethers.utils.parseEther(registerFee.toString())), - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - - it("allows owner to withdraw to the treasury", async () => { - const withdrawalAmount = ethers.utils.parseEther(registerFee.toString()) - - expect( - await ethers.provider.getBalance(hardhatProxyRegister.address), - ).to.equal(withdrawalAmount) - - const initialTreasuryBalance = await ethers.provider.getBalance( - treasury.address, - ) - tx = await hardhatProxyRegister.connect(owner).withdraw(withdrawalAmount) - const receipt = await tx.wait() - const finalTreasuryBalance = await ethers.provider.getBalance( - treasury.address, - ) - const finalContractBalance = await ethers.provider.getBalance( - hardhatProxyRegister.address, - ) - - expect(finalTreasuryBalance).to.equal( - initialTreasuryBalance.add(withdrawalAmount), - ) - expect(finalContractBalance).to.equal(0) - }) - }) - }); - - context("Nominate a Proxy", function () { - describe("Add a nomination", function () { - it("New proxy nomination is added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - }) - - it("Duplicate proxy nomination not added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - await expect( - hardhatProxyRegister.connect(addr1).makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address has an existing nomination") - }) - - it("Duplicate proxy nomination not added, even to a new proxy address", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - await expect( - - hardhatProxyRegister.connect(addr1).makeNomination( addr3.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address has an existing nomination") - }) - - it("Proxy nomination where nominator is an existing proxy not added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr2).makeNomination( addr3.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address is already acting as a proxy") - - }) - - it("Proxy nomination for an address that is already a proxy not added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr3).makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address is already acting as a proxy") - }) - - it("Address cannot be proxied to itself", async () => { - await expect( - hardhatProxyRegister.connect(addr1).makeNomination( addr1.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Proxy address cannot be the same as Nominator address") - }) - }) - }) - - context("Accept a Nomination", function () { - describe("Accept the Nomination", function () { - - it("Can do if valid", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const registerEntry2 = await hardhatProxyRegister.connect(addr2).getProxyRecordForCaller() - expect(registerEntry2[0]).to.equal(addr1.address) - expect(registerEntry2[1]).to.equal(addr2.address) - expect(registerEntry2[2]).to.equal(addr3.address) - - }) - - it("Cannot do for non-existent nomination", async () => { - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Caller is not the nominated proxy for this nominator") - }) - - it("Cannot do for another address's nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Caller is not the nominated proxy for this nominator") - }) - - it("Cannot do for a nominator that is now a proxy", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr1.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx2).to.emit(hardhatProxyRegister, "NominationMade") - - var tx3 = await hardhatProxyRegister - .connect(addr1) - .acceptNomination( addr3.address, addr3.address, 1,) - expect(tx3).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Address is already acting as a proxy") - }) - - it("Cannot do for address that is already a proxy", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx3 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr3.address, addr3.address, 1,) - expect(tx3).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Address is already acting as a proxy") - }) - }) - }); - - - context("Change a proxy entry", function () { - - describe("Change delivery address", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const registerEntry2 = await hardhatProxyRegister.connect(addr2).getProxyRecordForCaller() - expect(registerEntry2[0]).to.equal(addr1.address) - expect(registerEntry2[1]).to.equal(addr2.address) - expect(registerEntry2[2]).to.equal(addr3.address) - }) - - it("Proxy Address can update delivery", async () => { - const previousRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(previousRegistryEntry[0]).to.equal(addr1.address) - expect(previousRegistryEntry[1]).to.equal(addr2.address) - expect(previousRegistryEntry[2]).to.equal(addr3.address) - - var tx1 = await hardhatProxyRegister - .connect(addr2) - .updateDeliveryAddress( addr1.address, 1,) - expect(tx1).to.emit(hardhatProxyRegister, "DeliveryUpdated") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - expect(receipt.events[0].args.delivery).to.equal(addr1.address) - expect(receipt.events[0].args.oldDelivery).to.equal(addr3.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const newRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(newRegistryEntry[0]).to.equal(addr1.address) - expect(newRegistryEntry[1]).to.equal(addr2.address) - expect(newRegistryEntry[2]).to.equal(addr1.address) - }) - - it("Nominator cannot edit an entry", async () => { - const previousRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(previousRegistryEntry[0]).to.equal(addr1.address) - expect(previousRegistryEntry[1]).to.equal(addr2.address) - expect(previousRegistryEntry[2]).to.equal(addr3.address) - - await expect( - hardhatProxyRegister.connect(addr1).updateDeliveryAddress( addr1.address, 1,) - ).to.be.revertedWith("Proxy entry does not exist") - }) - - it("Another address cannot edit an entry", async () => { - const previousRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(previousRegistryEntry[0]).to.equal(addr1.address) - expect(previousRegistryEntry[1]).to.equal(addr2.address) - expect(previousRegistryEntry[2]).to.equal(addr3.address) - - await expect( - hardhatProxyRegister.connect(addr3).updateDeliveryAddress( addr1.address, 1,) - ).to.be.revertedWith("Proxy entry does not exist") - }) - }) - }); - - context("Delete a proxy entry", function () { - - describe("Delete called from Nominator", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - }) - - it("Removes a nomination only", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - var tx1 = await hardhatProxyRegister - .connect(addr1) - .deleteRecordByNominator(1,) - expect(tx1).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry2).to.equal(ZERO_ADDRESS) - }) - - it("Doesn't remove a proxy entry that is for another nominator", async () => { - // edge case test, but you can have two addresses both nominate a third address - // as a proxy. This is OK. Let's say the proxy accepts the second nomination. If the - // first address (the nomination that wasn't accepted) deletes that nominoation we DON'T - // want that to delete the second nominator's valud proxy entry, so let's check that - // doesn't happen - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - // Now load a proxy up from address 3 to address 2: - var tx1 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr3.address, addr1.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - // Verify that addr3 has the proxy with 2: - const registerEntry1 = await hardhatProxyRegister.connect(addr3).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr3.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr1.address) - - // Delete the nomination from address 1: - var tx3 = await hardhatProxyRegister - .connect(addr1) - .deleteRecordByNominator(1,) - expect(tx3).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry2).to.equal(ZERO_ADDRESS) - - // Verify that addr3 has the proxy with 2: - const registerEntry3 = await hardhatProxyRegister.connect(addr3).getNominatorRecordForCaller() - expect(registerEntry3[0]).to.equal(addr3.address) - expect(registerEntry3[1]).to.equal(addr2.address) - expect(registerEntry3[2]).to.equal(addr1.address) - }) - - it("Removes nomination and proxy register Item", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - const proxyRecordExists = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false) - - const proxyEntryExists1 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists1).to.equal(false) - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const proxyEntryExists2 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists2).to.equal(true) - - const proxyEntryExists3 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists3).to.equal(true) - - var tx1 = await hardhatProxyRegister - .connect(addr1) - .deleteRecordByNominator(1,) - expect(tx1).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry2).to.equal(ZERO_ADDRESS) - - const proxyEntryExists4 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists4).to.equal(false) - - const proxyEntryExists5 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists5).to.equal(false) - }) - }) - - describe("Delete called from Proxy", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - }) - - it("Removes nomination and proxy register Item", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - const proxyRecordExists = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false) - - const proxyEntryExists1 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists1).to.equal(false) - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const proxyEntryExists2 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists2).to.equal(true) - - const proxyEntryExists3 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists3).to.equal(true) - - var tx1 = await hardhatProxyRegister - .connect(addr2) - .deleteRecordByProxy(1,) - expect(tx1).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry2[0]).to.equal(ZERO_ADDRESS) - expect(registerEntry2[1]).to.equal(ZERO_ADDRESS) - expect(registerEntry2[2]).to.equal(ZERO_ADDRESS) - - const proxyEntryExists4 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists4).to.equal(false) - - const proxyEntryExists5 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists5).to.equal(false) - }) - - it("Call from Nominator does not work", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - const proxyRecordExists = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false) - - const proxyEntryExists1 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists1).to.equal(false) - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const proxyEntryExists2 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists2).to.equal(true) - - const proxyEntryExists3 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists3).to.equal(true) - - await expect( - hardhatProxyRegister.connect(addr1).deleteRecordByProxy(1,) - ).to.be.revertedWith("Proxy entry does not exist") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry2[0]).to.equal(addr1.address) - expect(registerEntry2[1]).to.equal(addr2.address) - expect(registerEntry2[2]).to.equal(addr3.address) - - const proxyEntryExists4 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists4).to.equal(true) - - const proxyEntryExists5 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists5).to.equal(true) - }) - }) - }); - - context("Getter Functions", function () { - - describe("Fee and Treasury", function () { - it("getRegisterFee", async () => { - const fee = await hardhatProxyRegister.getRegisterFee() - expect(fee).to.equal(ethers.utils.parseEther(registerFee.toString())); - }) - - it("getTreasuryAddress", async () => { - const treasuryAddress = await hardhatProxyRegister.getTreasuryAddress() - expect(treasuryAddress).to.equal(treasury.address); - }) - }) - - describe("No proxy details saved", function () { - it("nominationExists", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(false); - }) - - it("nominationExistsForCaller", async () => { - const nominationExists = await hardhatProxyRegister.connect(addr1.address).nominationExistsForCaller() - expect(nominationExists).to.equal(false); - }) - - it("getNomination", async () => { - const proxy = await hardhatProxyRegister.getNomination(addr1.address) - expect(proxy).to.equal(ZERO_ADDRESS); - }) - - it("getNominationForCaller", async () => { - const proxy = await hardhatProxyRegister.getNominationForCaller() - expect(proxy).to.equal(ZERO_ADDRESS); - }) - - it("proxyRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.proxyRecordExists(addr2.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("proxyRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).proxyRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.nominatorRecordExists(addr1.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr1.address).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("addressIsActive", async () => { - const addressIsActive = await hardhatProxyRegister.addressIsActive(addr1.address) - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.addressIsActive(addr2.address) - expect(addressIsActive2).to.equal(false); - }) - - it("addressIsActiveForCaller", async () => { - const addressIsActive = await hardhatProxyRegister.connect(addr1.address).addressIsActiveForCaller() - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.connect(addr2.address).addressIsActiveForCaller() - expect(addressIsActive2).to.equal(false); - }) - - it("getProxyRecord", async () => { - const entry = await hardhatProxyRegister.getProxyRecord(addr2.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getProxyRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getProxyRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecord", async () => { - const entry = await hardhatProxyRegister.getNominatorRecord(addr1.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr1.address).getNominatorRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getAddresses", async () => { - const entry = await hardhatProxyRegister.getAddresses(addr2.address) - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - - it("getAddressesForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getAddressesForCaller() - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - }) - - describe("With Proxy Nomination", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - }) - - it("nominationExists", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(true); - }) - - it("nominationExistsForCaller", async () => { - const nominationExists = await hardhatProxyRegister.connect(addr1.address).nominationExistsForCaller() - expect(nominationExists).to.equal(true); - }) - - it("getNomination", async () => { - const proxy = await hardhatProxyRegister.getNomination(addr1.address) - expect(proxy).to.equal(addr2.address); - }) - - it("getNominationForCaller", async () => { - const proxy = await hardhatProxyRegister.connect(addr1.address).getNominationForCaller() - expect(proxy).to.equal(addr2.address); - }) - - it("proxyRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.proxyRecordExists(addr2.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("proxyRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).proxyRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.nominatorRecordExists(addr1.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("addressIsActive", async () => { - const addressIsActive = await hardhatProxyRegister.addressIsActive(addr1.address) - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.addressIsActive(addr2.address) - expect(addressIsActive2).to.equal(false); - }) - - it("addressIsActiveForCaller", async () => { - const addressIsActive = await hardhatProxyRegister.connect(addr1.address).addressIsActiveForCaller() - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.connect(addr2.address).addressIsActiveForCaller() - expect(addressIsActive2).to.equal(false); - }) - - it("getProxyRecord", async () => { - const entry = await hardhatProxyRegister.getProxyRecord(addr2.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getProxyRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getProxyRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecord", async () => { - const entry = await hardhatProxyRegister.getNominatorRecord(addr1.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr1.address).getNominatorRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getAddresses", async () => { - const entry = await hardhatProxyRegister.getAddresses(addr2.address) - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - - it("getAddressesForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getAddressesForCaller() - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - }) - - describe("With Proxy Active", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - }) - - it("nominationExists", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(true); - }) - - it("nominationExistsForCaller", async () => { - const nominationExists = await hardhatProxyRegister.connect(addr1.address).nominationExistsForCaller() - expect(nominationExists).to.equal(true); - }) - - it("getNomination", async () => { - const proxy = await hardhatProxyRegister.getNomination(addr1.address) - expect(proxy).to.equal(addr2.address); - }) - - it("getNominationForCaller", async () => { - const proxy = await hardhatProxyRegister.connect(addr1.address).getNominationForCaller() - expect(proxy).to.equal(addr2.address); - }) - - it("proxyRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.proxyRecordExists(addr2.address) - expect(proxyRecordExists).to.equal(true); - }) - - it("proxyRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).proxyRecordExistsForCaller() - expect(proxyRecordExists).to.equal(true); - }) - - it("nominatorRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.nominatorRecordExists(addr1.address) - expect(proxyRecordExists).to.equal(true); - }) - - it("nominatorRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr1.address).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(true); - }) - - it("addressIsActive", async () => { - const addressIsActive = await hardhatProxyRegister.addressIsActive(addr1.address) - expect(addressIsActive).to.equal(true); - - const addressIsActive2 = await hardhatProxyRegister.addressIsActive(addr2.address) - expect(addressIsActive2).to.equal(true); - }) - - it("addressIsActiveForCaller", async () => { - const addressIsActive = await hardhatProxyRegister.connect(addr1.address).addressIsActiveForCaller() - expect(addressIsActive).to.equal(true); - - const addressIsActive2 = await hardhatProxyRegister.connect(addr2.address).addressIsActiveForCaller() - expect(addressIsActive2).to.equal(true); - }) - - it("getProxyRecord", async () => { - const entry = await hardhatProxyRegister.getProxyRecord(addr2.address) - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getProxyRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getProxyRecordForCaller() - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getNominatorRecord", async () => { - const entry = await hardhatProxyRegister.getNominatorRecord(addr1.address) - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getNominatorRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr1.address).getNominatorRecordForCaller() - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getAddresses", async () => { - const entry = await hardhatProxyRegister.getAddresses(addr2.address) - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr3.address); - expect(entry[2]).to.equal(true); - }) - - it("getAddressesForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getAddressesForCaller() - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr3.address); - expect(entry[2]).to.equal(true); - }) - - it("getAddresses for nominator with valid proxy", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(true); - await expect( - hardhatProxyRegister.getAddresses(addr1.address) - ).to.be.revertedWith("Nominator address cannot interact directly, only through the proxy address") - }) - - it("getAddressesForCaller for nominator with valid proxy", async () => { - await expect( - hardhatProxyRegister.connect(addr1.address).getAddressesForCaller() - ).to.be.revertedWith("Nominator address cannot interact directly, only through the proxy address") - }) - }) - - describe("Role", function () { - it("Returns none with no proxy or nomination", async () => { - var role = await hardhatProxyRegister.getRole(addr1.address) - expect(role).to.equal("None"); - - role = await hardhatProxyRegister.connect(addr1.address).getRoleForCaller() - expect(role).to.equal("None"); - }) - - it("Returns pending with unaccepted nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var role = await hardhatProxyRegister.getRole(addr1.address) - expect(role).to.equal("Nominator - Proxy Pending"); - - role = await hardhatProxyRegister.connect(addr1.address).getRoleForCaller() - expect(role).to.equal("Nominator - Proxy Pending"); - }) - - it("Returns active with accepted nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - var role = await hardhatProxyRegister.getRole(addr1.address) - expect(role).to.equal("Nominator - Proxy Active"); - - role = await hardhatProxyRegister.connect(addr1.address).getRoleForCaller() - expect(role).to.equal("Nominator - Proxy Active"); - }) - - it("Returns proxy with unaccepted nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - var role = await hardhatProxyRegister.getRole(addr2.address) - expect(role).to.equal("Proxy"); - - role = await hardhatProxyRegister.connect(addr2.address).getRoleForCaller() - expect(role).to.equal("Proxy"); - }) - }) - - }); -}) diff --git a/assets/eip-4907/.gitignore b/assets/eip-4907/.gitignore deleted file mode 100644 index 504afef..0000000 --- a/assets/eip-4907/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -package-lock.json diff --git a/assets/eip-4907/README.md b/assets/eip-4907/README.md deleted file mode 100644 index 720f084..0000000 --- a/assets/eip-4907/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# EIP-4907 -EIP-4907 is an extension of ERC-721. It proposes an additional role **user** and a valid duration indicator **expires**. It allows users and developers manage the use right more simple and efficient. - -### Tools -* [Visual Studio Code](https://code.visualstudio.com/) -* [Solidity](https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity) - Solidity support for Visual Studio code -* [Truffle](https://truffleframework.com/) - the most popular development framework for Ethereum - -### Install -``` -npm install -``` - -### Test -``` -truffle test -``` - -### Additional Resources -* [Official Truffle Documentation](http://truffleframework.com/docs/) for complete and detailed guides, tips, and sample code. \ No newline at end of file diff --git a/assets/eip-4907/contracts/ERC4907.sol b/assets/eip-4907/contracts/ERC4907.sol deleted file mode 100644 index 6bc044f..0000000 --- a/assets/eip-4907/contracts/ERC4907.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC4907.sol"; - -contract ERC4907 is ERC721, IERC4907 { - struct UserInfo - { - address user; // address of user role - uint64 expires; // unix timestamp, user expires - } - - mapping (uint256 => UserInfo) internal _users; - - constructor(string memory name_, string memory symbol_) - ERC721(name_,symbol_) - { - } - - /// @notice set the user and expires of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - function setUser(uint256 tokenId, address user, uint64 expires) public virtual{ - require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: transfer caller is not owner nor approved"); - UserInfo storage info = _users[tokenId]; - info.user = user; - info.expires = expires; - emit UpdateUser(tokenId,user,expires); - } - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId)public view virtual returns(address){ - if( uint256(_users[tokenId].expires) >= block.timestamp){ - return _users[tokenId].user; - } - else{ - return address(0); - } - } - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) public view virtual returns(uint256){ - return _users[tokenId].expires; - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC4907).interfaceId || super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId); - - if (from != to && _users[tokenId].user != address(0)) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0); - } - } -} diff --git a/assets/eip-4907/contracts/ERC4907Demo.sol b/assets/eip-4907/contracts/ERC4907Demo.sol deleted file mode 100644 index daa6d99..0000000 --- a/assets/eip-4907/contracts/ERC4907Demo.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC4907.sol"; - -contract ERC4907Demo is ERC4907 { - - constructor(string memory name_, string memory symbol_) - ERC4907(name_,symbol_) - { - } - - function mint(uint256 tokenId, address to) public { - _mint(to, tokenId); - } - -} diff --git a/assets/eip-4907/contracts/IERC4907.sol b/assets/eip-4907/contracts/IERC4907.sol deleted file mode 100644 index 9858dfe..0000000 --- a/assets/eip-4907/contracts/IERC4907.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC4907 { - // Logged when the user of a token assigns a new user or updates expires - /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed - /// The zero address for user indicates that there is no user address - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires); - - /// @notice set the user and expires of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - function setUser(uint256 tokenId, address user, uint64 expires) external ; - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId) external view returns(address); - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) external view returns(uint256); -} diff --git a/assets/eip-4907/migrations/1_initial_migration.js b/assets/eip-4907/migrations/1_initial_migration.js deleted file mode 100644 index 1eb915e..0000000 --- a/assets/eip-4907/migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -const ERC4907Demo = artifacts.require("ERC4907Demo"); - -module.exports = function (deployer) { - deployer.deploy(ERC4907Demo, "ERC4907Demo", "ERC4907Demo"); -}; diff --git a/assets/eip-4907/package.json b/assets/eip-4907/package.json deleted file mode 100644 index 9104d3d..0000000 --- a/assets/eip-4907/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "ERC-4907", - "version": "1.0.0", - "description": "", - "main": "truffle-config.js", - "directories": { - "test": "test" - }, - "scripts": {}, - "keywords": [], - "author": "", - "license": "CC0-1.0", - "dependencies": { - "@openzeppelin/contracts": "^4.3.3", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "chai": "^4.3.6" - } -} diff --git a/assets/eip-4907/test/test.js b/assets/eip-4907/test/test.js deleted file mode 100644 index f2df2ed..0000000 --- a/assets/eip-4907/test/test.js +++ /dev/null @@ -1,25 +0,0 @@ -const { assert } = require("chai"); - -const ERC4907Demo = artifacts.require("ERC4907Demo"); - -contract("test", async (accounts) => { - it("should set user to Bob", async () => { - // Get initial balances of first and second account. - const Alice = accounts[0]; - const Bob = accounts[1]; - - const instance = await ERC4907Demo.deployed("T", "T"); - const demo = instance; - - await demo.mint(1, Alice); - let expires = Math.floor(new Date().getTime() / 1000) + 1000; - await demo.setUser(1, Bob, BigInt(expires)); - - let user_1 = await demo.userOf(1); - - assert.equal(user_1, Bob, "User of NFT 1 should be Bob"); - - let owner_1 = await demo.ownerOf(1); - assert.equal(owner_1, Alice, "Owner of NFT 1 should be Alice"); - }); -}); diff --git a/assets/eip-4907/truffle-config.js b/assets/eip-4907/truffle-config.js deleted file mode 100644 index 3517e05..0000000 --- a/assets/eip-4907/truffle-config.js +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Use this file to configure your truffle project. It's seeded with some - * common settings for different networks and features like migrations, - * compilation and testing. Uncomment the ones you need or modify - * them to suit your project as necessary. - * - * More information about configuration can be found at: - * - * trufflesuite.com/docs/advanced/configuration - * - * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) - * to sign your transactions before they're sent to a remote public node. Infura accounts - * are available for free at: infura.io/register. - * - * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate - * public/private key pairs. If you're publishing your code to GitHub make sure you load this - * phrase from a file you've .gitignored so it doesn't accidentally become public. - * - */ - -// const HDWalletProvider = require('@truffle/hdwallet-provider'); -// -// const fs = require('fs'); -// const mnemonic = fs.readFileSync(".secret").toString().trim(); - -module.exports = { - /** - * Networks define how you connect to your ethereum client and let you set the - * defaults web3 uses to send transactions. If you don't specify one truffle - * will spin up a development blockchain for you on port 9545 when you - * run `develop` or `test`. You can ask a truffle command to use a specific - * network from the command line, e.g - * - * $ truffle test --network - */ - - networks: { - // Useful for testing. The `development` name is special - truffle uses it by default - // if it's defined here and no other network is specified at the command line. - // You should run a client (like ganache-cli, geth or parity) in a separate terminal - // tab if you use this network and you must also set the `host`, `port` and `network_id` - // options below to some value. - // - // development: { - // host: "127.0.0.1", // Localhost (default: none) - // port: 8545, // Standard Ethereum port (default: none) - // network_id: "*", // Any network (default: none) - // }, - // Another network with more advanced options... - // advanced: { - // port: 8777, // Custom port - // network_id: 1342, // Custom network - // gas: 8500000, // Gas sent with each transaction (default: ~6700000) - // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) - // from:
, // Account to send txs from (default: accounts[0]) - // websocket: true // Enable EventEmitter interface for web3 (default: false) - // }, - // Useful for deploying to a public network. - // NB: It's important to wrap the provider as a function. - // ropsten: { - // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), - // network_id: 3, // Ropsten's id - // gas: 5500000, // Ropsten has a lower block limit than mainnet - // confirmations: 2, // # of confs to wait between deployments. (default: 0) - // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) - // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) - // }, - // Useful for private networks - // private: { - // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), - // network_id: 2111, // This network is yours, in the cloud. - // production: true // Treats this network as if it was a public net. (default: false) - // } - }, - - // Set default mocha options here, use special reporters etc. - mocha: { - // timeout: 100000 - }, - - // Configure your compilers - compilers: { - solc: { - version: "0.8.10", // Fetch exact version from solc-bin (default: truffle's version) - // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) - settings: { - // See the solidity docs for advice about optimization and evmVersion - optimizer: { - enabled: false, - runs: 200, - }, - // , - // evmVersion: "byzantium" - // } - }, - }, - - // Truffle DB is currently disabled by default; to enable it, change enabled: - // false to enabled: true. The default storage location can also be - // overridden by specifying the adapter settings, as shown in the commented code below. - // - // NOTE: It is not possible to migrate your contracts to truffle DB and you should - // make a backup of your artifacts to a safe location before enabling this feature. - // - // After you backed up your artifacts you can utilize db by running migrate as follows: - // $ truffle migrate --reset --compile-all - // - // db: { - // enabled: false, - // host: "127.0.0.1", - // adapter: { - // name: "sqlite", - // settings: { - // directory: ".db" - // } - // } - }, -}; diff --git a/assets/eip-4910/eip-4910-print-families.png b/assets/eip-4910/eip-4910-print-families.png deleted file mode 100644 index 30815ee..0000000 Binary files a/assets/eip-4910/eip-4910-print-families.png and /dev/null differ diff --git a/assets/eip-4910/eip-4910-royalties.png b/assets/eip-4910/eip-4910-royalties.png deleted file mode 100644 index ddf6951..0000000 Binary files a/assets/eip-4910/eip-4910-royalties.png and /dev/null differ diff --git a/assets/eip-4955/different-renders.jpeg b/assets/eip-4955/different-renders.jpeg deleted file mode 100644 index 6a36d11..0000000 Binary files a/assets/eip-4955/different-renders.jpeg and /dev/null differ diff --git a/assets/eip-4973/ERC4973-flat.sol b/assets/eip-4973/ERC4973-flat.sol deleted file mode 100644 index e20aa82..0000000 --- a/assets/eip-4973/ERC4973-flat.sol +++ /dev/null @@ -1,1028 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.8; - -// OpenZeppelin Contracts (last updated v4.7.1) (utils/cryptography/SignatureChecker.sol) - -// OpenZeppelin Contracts (last updated v4.7.3) (utils/cryptography/ECDSA.sol) - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) - -/** - * @dev String operations. - */ -library Strings { - bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; - uint8 private constant _ADDRESS_LENGTH = 20; - - /** - * @dev Converts a `uint256` to its ASCII `string` decimal representation. - */ - function toString(uint256 value) internal pure returns (string memory) { - // Inspired by OraclizeAPI's implementation - MIT licence - // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol - - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. - */ - function toHexString(uint256 value) internal pure returns (string memory) { - if (value == 0) { - return "0x00"; - } - uint256 temp = value; - uint256 length = 0; - while (temp != 0) { - length++; - temp >>= 8; - } - return toHexString(value, length); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. - */ - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _HEX_SYMBOLS[value & 0xf]; - value >>= 4; - } - require(value == 0, "Strings: hex length insufficient"); - return string(buffer); - } - - /** - * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. - */ - function toHexString(address addr) internal pure returns (string memory) { - return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); - } -} - -/** - * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. - * - * These functions can be used to verify that a message was signed by the holder - * of the private keys of a given address. - */ -library ECDSA { - enum RecoverError { - NoError, - InvalidSignature, - InvalidSignatureLength, - InvalidSignatureS, - InvalidSignatureV - } - - function _throwError(RecoverError error) private pure { - if (error == RecoverError.NoError) { - return; // no error: do nothing - } else if (error == RecoverError.InvalidSignature) { - revert("ECDSA: invalid signature"); - } else if (error == RecoverError.InvalidSignatureLength) { - revert("ECDSA: invalid signature length"); - } else if (error == RecoverError.InvalidSignatureS) { - revert("ECDSA: invalid signature 's' value"); - } else if (error == RecoverError.InvalidSignatureV) { - revert("ECDSA: invalid signature 'v' value"); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature` or error string. This address can then be used for verification purposes. - * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. - * - * Documentation for signature generation: - * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] - * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] - * - * _Available since v4.3._ - */ - function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { - if (signature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - // ecrecover takes the signature parameters, and the only way to get them - // currently is to use assembly. - /// @solidity memory-safe-assembly - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - return tryRecover(hash, v, r, s); - } else { - return (address(0), RecoverError.InvalidSignatureLength); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature`. This address can then be used for verification purposes. - * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. - */ - function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, signature); - _throwError(error); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. - * - * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] - * - * _Available since v4.3._ - */ - function tryRecover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address, RecoverError) { - bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - uint8 v = uint8((uint256(vs) >> 255) + 27); - return tryRecover(hash, v, r, s); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. - * - * _Available since v4.2._ - */ - function recover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, r, vs); - _throwError(error); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `v`, - * `r` and `s` signature fields separately. - * - * _Available since v4.3._ - */ - function tryRecover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address, RecoverError) { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return (address(0), RecoverError.InvalidSignatureS); - } - if (v != 27 && v != 28) { - return (address(0), RecoverError.InvalidSignatureV); - } - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(hash, v, r, s); - if (signer == address(0)) { - return (address(0), RecoverError.InvalidSignature); - } - - return (signer, RecoverError.NoError); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `v`, - * `r` and `s` signature fields separately. - */ - function recover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, v, r, s); - _throwError(error); - return recovered; - } - - /** - * @dev Returns an Ethereum Signed Message, created from a `hash`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. - */ - function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { - // 32 is the length in bytes of hash, - // enforced by the type signature above - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); - } - - /** - * @dev Returns an Ethereum Signed Message, created from `s`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. - */ - function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s)); - } - - /** - * @dev Returns an Ethereum Signed Typed Data, created from a - * `domainSeparator` and a `structHash`. This produces hash corresponding - * to the one signed with the - * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] - * JSON-RPC method as part of EIP-712. - * - * See {recover}. - */ - function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - } -} - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCall(target, data, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - require(isContract(target), "Address: call to non-contract"); - - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - require(isContract(target), "Address: static call to non-contract"); - - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - require(isContract(target), "Address: delegate call to non-contract"); - - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} - -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) - -/** - * @dev Interface of the ERC1271 standard signature validation method for - * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. - * - * _Available since v4.1._ - */ -interface IERC1271 { - /** - * @dev Should return whether the signature provided is valid for the provided data - * @param hash Hash of the data to be signed - * @param signature Signature byte array associated with _data - */ - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); -} - -/** - * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA - * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like - * Argent and Gnosis Safe. - * - * _Available since v4.1._ - */ -library SignatureChecker { - /** - * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the - * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`. - * - * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus - * change through time. It could return true at block N and false at block N+1 (or the opposite). - */ - function isValidSignatureNow( - address signer, - bytes32 hash, - bytes memory signature - ) internal view returns (bool) { - (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature); - if (error == ECDSA.RecoverError.NoError && recovered == signer) { - return true; - } - - (bool success, bytes memory result) = signer.staticcall( - abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature) - ); - return (success && - result.length == 32 && - abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)); - } -} - -// OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol) - -/** - * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. - * - * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, - * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding - * they need in their contracts using a combination of `abi.encode` and `keccak256`. - * - * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding - * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA - * ({_hashTypedDataV4}). - * - * The implementation of the domain separator was designed to be as efficient as possible while still properly updating - * the chain id to protect against replay attacks on an eventual fork of the chain. - * - * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method - * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. - * - * _Available since v3.4._ - */ -abstract contract EIP712 { - /* solhint-disable var-name-mixedcase */ - // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to - // invalidate the cached domain separator if the chain id changes. - bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; - uint256 private immutable _CACHED_CHAIN_ID; - address private immutable _CACHED_THIS; - - bytes32 private immutable _HASHED_NAME; - bytes32 private immutable _HASHED_VERSION; - bytes32 private immutable _TYPE_HASH; - - /* solhint-enable var-name-mixedcase */ - - /** - * @dev Initializes the domain separator and parameter caches. - * - * The meaning of `name` and `version` is specified in - * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: - * - * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. - * - `version`: the current major version of the signing domain. - * - * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart - * contract upgrade]. - */ - constructor(string memory name, string memory version) { - bytes32 hashedName = keccak256(bytes(name)); - bytes32 hashedVersion = keccak256(bytes(version)); - bytes32 typeHash = keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ); - _HASHED_NAME = hashedName; - _HASHED_VERSION = hashedVersion; - _CACHED_CHAIN_ID = block.chainid; - _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion); - _CACHED_THIS = address(this); - _TYPE_HASH = typeHash; - } - - /** - * @dev Returns the domain separator for the current chain. - */ - function _domainSeparatorV4() internal view returns (bytes32) { - if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) { - return _CACHED_DOMAIN_SEPARATOR; - } else { - return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); - } - } - - function _buildDomainSeparator( - bytes32 typeHash, - bytes32 nameHash, - bytes32 versionHash - ) private view returns (bytes32) { - return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this))); - } - - /** - * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this - * function returns the hash of the fully encoded EIP712 message for this domain. - * - * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: - * - * ```solidity - * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( - * keccak256("Mail(address to,string contents)"), - * mailTo, - * keccak256(bytes(mailContents)) - * ))); - * address signer = ECDSA.recover(digest, signature); - * ``` - */ - function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { - return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); - } -} - -// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) - -// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) - -/** - * @dev Interface of the ERC165 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-165[EIP]. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others ({ERC165Checker}). - * - * For an implementation, see {ERC165}. - */ -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -/** - * @dev Implementation of the {IERC165} interface. - * - * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check - * for the additional interface id that will be supported. For example: - * - * ```solidity - * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); - * } - * ``` - * - * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. - */ -abstract contract ERC165 is IERC165 { - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC165).interfaceId; - } -} - -// OpenZeppelin Contracts v4.4.1 (utils/structs/BitMaps.sol) - -/** - * @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. - * Largelly inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor]. - */ -library BitMaps { - struct BitMap { - mapping(uint256 => uint256) _data; - } - - /** - * @dev Returns whether the bit at `index` is set. - */ - function get(BitMap storage bitmap, uint256 index) internal view returns (bool) { - uint256 bucket = index >> 8; - uint256 mask = 1 << (index & 0xff); - return bitmap._data[bucket] & mask != 0; - } - - /** - * @dev Sets the bit at `index` to the boolean `value`. - */ - function setTo( - BitMap storage bitmap, - uint256 index, - bool value - ) internal { - if (value) { - set(bitmap, index); - } else { - unset(bitmap, index); - } - } - - /** - * @dev Sets the bit at `index`. - */ - function set(BitMap storage bitmap, uint256 index) internal { - uint256 bucket = index >> 8; - uint256 mask = 1 << (index & 0xff); - bitmap._data[bucket] |= mask; - } - - /** - * @dev Unsets the bit at `index`. - */ - function unset(BitMap storage bitmap, uint256 index) internal { - uint256 bucket = index >> 8; - uint256 mask = 1 << (index & 0xff); - bitmap._data[bucket] &= ~mask; - } -} - -interface IERC721Metadata { - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function tokenURI(uint256 tokenId) external view returns (string memory); -} - -/// @title Account-bound tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-4973 -/// Note: the ERC-165 identifier for this interface is 0xeb72bb7c -interface IERC4973 { - /// @dev This emits when ownership of any ABT changes by any mechanism. - /// This event emits when ABTs are given or equipped and unequipped - /// (`to` == 0). - event Transfer( - address indexed from, address indexed to, uint256 indexed tokenId - ); - /// @notice Count all ABTs assigned to an owner - /// @dev ABTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param owner An address for whom to query the balance - /// @return The number of ABTs owned by `address owner`, possibly zero - - function balanceOf(address owner) external view returns (uint256); - /// @notice Find the address bound to an ERC4973 account-bound token - /// @dev ABTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param tokenId The identifier for an ABT. - /// @return The address of the owner bound to the ABT. - function ownerOf(uint256 tokenId) external view returns (address); - /// @notice Removes the `uint256 tokenId` from an account. At any time, an - /// ABT receiver must be able to disassociate themselves from an ABT - /// publicly through calling this function. After successfully executing this - /// function, given the parameters for calling `function give` or - /// `function take` a token must be re-equipable. - /// @dev Must emit a `event Transfer` with the `address to` field pointing to - /// the zero address. - /// @param tokenId The identifier for an ABT. - function unequip(uint256 tokenId) external; - /// @notice Creates and transfers the ownership of an ABT from the - /// transaction's `msg.sender` to `address to`. - /// @dev Throws unless `bytes signature` represents a signature of the - // EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` expressing - /// `address to`'s explicit agreement to be publicly associated with - /// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be - /// generated by type-casting the `bytes32` EIP-712 structured data hash to a - /// `uint256`. If `bytes signature` is empty or `address to` is a contract, - /// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must - /// be made to `address to`. A successful execution must result in the - /// `event Transfer(msg.sender, to, tokenId)`. Once an ABT exists as an - /// `uint256 tokenId` in the contract, `function give(...)` must throw. - /// @param to The receiver of the ABT. - /// @param metadata The metadata that will be associated to the ABT. - /// @param signature A signature of the EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` signed by - /// `address to`. - /// @return A unique `uint256 tokenId` generated by type-casting the `bytes32` - /// EIP-712 structured data hash to a `uint256`. - function give(address to, bytes calldata metadata, bytes calldata signature) - external - returns (uint256); - /// @notice Creates and transfers the ownership of an ABT from an - /// `address from` to the transaction's `msg.sender`. - /// @dev Throws unless `bytes signature` represents a signature of the - /// EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` expressing - /// `address from`'s explicit agreement to be publicly associated with - /// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be - /// generated by type-casting the `bytes32` EIP-712 structured data hash to a - /// `uint256`. If `bytes signature` is empty or `address from` is a contract, - /// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must - /// be made to `address from`. A successful execution must result in the - /// emission of an `event Transfer(from, msg.sender, tokenId)`. Once an ABT - /// exists as an `uint256 tokenId` in the contract, `function take(...)` must - /// throw. - /// @param from The origin of the ABT. - /// @param metadata The metadata that will be associated to the ABT. - /// @param signature A signature of the EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` signed by - /// `address from`. - /// @return A unique `uint256 tokenId` generated by type-casting the `bytes32` - /// EIP-712 structured data hash to a `uint256`. - function take(address from, bytes calldata metadata, bytes calldata signature) - external - returns (uint256); - /// @notice Decodes the opaque metadata bytestring of an ABT into the token - /// URI that will be associated with it once it is created on chain. - /// @param metadata The metadata that will be associated to an ABT. - /// @return A URI that represents the metadata. - function decodeURI(bytes calldata metadata) external returns (string memory); -} - -bytes32 constant AGREEMENT_HASH = - keccak256("Agreement(address active,address passive,bytes metadata)"); - -/// @notice Reference implementation of EIP-4973 tokens. -/// @author Tim Daubenschütz, Rahul Rumalla (https://github.com/rugpullindex/ERC4973/blob/master/src/ERC4973.sol) -abstract contract ERC4973 is EIP712, ERC165, IERC721Metadata, IERC4973 { - using BitMaps for BitMaps.BitMap; - - BitMaps.BitMap private _usedHashes; - - string private _name; - string private _symbol; - - mapping(uint256 => address) private _owners; - mapping(uint256 => string) private _tokenURIs; - mapping(address => uint256) private _balances; - - constructor(string memory name_, string memory symbol_, string memory version) - EIP712(name_, version) - { - _name = name_; - _symbol = symbol_; - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return interfaceId == type(IERC721Metadata).interfaceId - || interfaceId == type(IERC4973).interfaceId - || super.supportsInterface(interfaceId); - } - - function name() public view virtual override returns (string memory) { - return _name; - } - - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - function tokenURI(uint256 tokenId) - public - view - virtual - override - returns (string memory) - { - require(_exists(tokenId), "tokenURI: token doesn't exist"); - return _tokenURIs[tokenId]; - } - - function unequip(uint256 tokenId) public virtual override { - require(msg.sender == ownerOf(tokenId), "unequip: sender must be owner"); - _usedHashes.unset(tokenId); - _burn(tokenId); - } - - function balanceOf(address owner) - public - view - virtual - override - returns (uint256) - { - require(owner != address(0), "balanceOf: address zero is not a valid owner"); - return _balances[owner]; - } - - function ownerOf(uint256 tokenId) public view virtual returns (address) { - address owner = _owners[tokenId]; - require(owner != address(0), "ownerOf: token doesn't exist"); - return owner; - } - - function give(address to, bytes calldata metadata, bytes calldata signature) - external - virtual - returns (uint256) - { - require(msg.sender != to, "give: cannot give from self"); - uint256 tokenId = _safeCheckAgreement(msg.sender, to, metadata, signature); - string memory uri = decodeURI(metadata); - _mint(msg.sender, to, tokenId, uri); - _usedHashes.set(tokenId); - return tokenId; - } - - function take(address from, bytes calldata metadata, bytes calldata signature) - external - virtual - returns (uint256) - { - require(msg.sender != from, "take: cannot take from self"); - uint256 tokenId = _safeCheckAgreement(msg.sender, from, metadata, signature); - string memory uri = decodeURI(metadata); - _mint(from, msg.sender, tokenId, uri); - _usedHashes.set(tokenId); - return tokenId; - } - - function decodeURI(bytes calldata metadata) - public - virtual - returns (string memory) - { - return string(metadata); - } - - function _safeCheckAgreement( - address active, - address passive, - bytes calldata metadata, - bytes calldata signature - ) - internal - virtual - returns (uint256) - { - bytes32 hash = _getHash(active, passive, metadata); - uint256 tokenId = uint256(hash); - - require( - SignatureChecker.isValidSignatureNow(passive, hash, signature), - "_safeCheckAgreement: invalid signature" - ); - require(!_usedHashes.get(tokenId), "_safeCheckAgreement: already used"); - return tokenId; - } - - function _getHash(address active, address passive, bytes calldata metadata) - internal - view - returns (bytes32) - { - bytes32 structHash = - keccak256(abi.encode(AGREEMENT_HASH, active, passive, keccak256(metadata))); - return _hashTypedDataV4(structHash); - } - - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _owners[tokenId] != address(0); - } - - function _mint(address from, address to, uint256 tokenId, string memory uri) - internal - virtual - returns (uint256) - { - require(!_exists(tokenId), "mint: tokenID exists"); - _balances[to] += 1; - _owners[tokenId] = to; - _tokenURIs[tokenId] = uri; - emit Transfer(from, to, tokenId); - return tokenId; - } - - function _burn(uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - - _balances[owner] -= 1; - delete _owners[tokenId]; - delete _tokenURIs[tokenId]; - - emit Transfer(owner, address(0), tokenId); - } -} - diff --git a/assets/eip-4973/package.json b/assets/eip-4973/package.json deleted file mode 100644 index 496d41a..0000000 --- a/assets/eip-4973/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "erc4973", - "version": "0.4.0", - "description": "A standard interface for non-transferrable non-fungible tokens, also known as \"account-bound\" or \"soulbound tokens\" or \"badges\".", - "files": [ - "/src/ERC4973.sol", - "/src/ERC165.sol", - "/src/interfaces/IERC165.sol", - "/src/interfaces/IERC4973.sol", - "/src/interfaces/IERC721Metadata.sol", - "/sdk/src/index.mjs" - ], - "scripts": { - "test": "forge test", - "test:sdk": "ava sdk/test", - "gen:flatfile": "forge flatten src/ERC4973.sol > ./assets/ERC4973-flat.sol", - "gen:sdk": "cp package.json assets/package.json && cp -r sdk/ assets/sdk", - "gen:assets": "npm run gen:flatfile && npm run gen:sdk" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/rugpullindex/ERC4973.git" - }, - "keywords": [ - "account-bound", - "soulbound", - "tokens", - "ethereum", - "eip", - "badges", - "non-transferrable" - ], - "author": "Tim Daubenschütz (https://timdaub.github.io/)", - "license": "CC0-1.0", - "bugs": { - "url": "https://github.com/rugpullindex/ERC4973/issues" - }, - "homepage": "https://github.com/rugpullindex/ERC4973#readme", - "dependencies": { - "ethers": "5.7.2" - }, - "devDependencies": { - "ava": "4.3.1" - } -} diff --git a/assets/eip-4973/sdk/src/index.mjs b/assets/eip-4973/sdk/src/index.mjs deleted file mode 100644 index dc94ae3..0000000 --- a/assets/eip-4973/sdk/src/index.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import { utils } from "ethers"; - -// See: https://docs.ethers.io/v5/api/signer/#Signer-signTypedData for more -// detailed instructions. -export async function generateSignature(signer, types, domain, agreement) { - const signature = await signer._signTypedData(domain, types, agreement); - const { r, s, v } = utils.splitSignature(signature); - return utils.solidityPack(["bytes32", "bytes32", "uint8"], [r, s, v]); -} diff --git a/assets/eip-4973/sdk/test/index_test.mjs b/assets/eip-4973/sdk/test/index_test.mjs deleted file mode 100644 index 2adf1fc..0000000 --- a/assets/eip-4973/sdk/test/index_test.mjs +++ /dev/null @@ -1,43 +0,0 @@ -// @format -import test from "ava"; - -import { Wallet, utils } from "ethers"; - -import { generateSignature } from "../src/index.mjs"; - -test("generating a compact signature for function give", async (t) => { - // from: https://docs.ethers.io/v5/api/signer/#Wallet--methods - const passiveAddress = "0x0f6A79A579658E401E0B81c6dde1F2cd51d97176"; - const passivePrivateKey = - "0xad54bdeade5537fb0a553190159783e45d02d316a992db05cbed606d3ca36b39"; - const signer = new Wallet(passivePrivateKey); - t.is(signer.address, passiveAddress); - - const types = { - Agreement: [ - { name: "active", type: "address" }, - { name: "passive", type: "address" }, - { name: "metadata", type: "bytes" }, - ], - }; - const domain = { - name: "Name", - version: "Version", - chainId: 31337, // the chainId of foundry - verifyingContract: "0xce71065d4017f316ec606fe4422e11eb2c47c246", - }; - - const agreement = { - active: "0xb4c79dab8f259c7aee6e5b2aa729821864227e84", - passive: passiveAddress, - metadata: utils.toUtf8Bytes("https://example.com/metadata.json"), - }; - - const signature = await generateSignature(signer, types, domain, agreement); - t.truthy(signature); - t.is(signature.length, 64 + 64 + 2 + 2); - t.is( - signature, - "0x4473afdec84287f10aa0b5eb608d360e2e9220bee657a4a5ca468e69a4de255c38691fca0c52f295d1831beaa0b7f079c1ab7959257578d2fb8d98740d9b0e111c" - ); -}); diff --git a/assets/eip-4974/ERC4974.sol b/assets/eip-4974/ERC4974.sol deleted file mode 100644 index 2619a2e..0000000 --- a/assets/eip-4974/ERC4974.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; -import "./IERC4974.sol"; - -/** - * See {IERC4974} - * Implements the ERC4974 Metadata extension. - */ -contract LoyaltyPoints is IERC4974 { - - // The address of the operator that can assign ratings - address private _operator; - - // Mapping of customer addresses to their ratings - mapping (bytes32 => int8) private _ratings; - - // Initializes the contract by setting the operator to msg.sender - constructor () { - _operator = msg.sender; - } - - // Set the operator address - // Only the current operator or the contract owner can call this function - function setOperator(address newOperator) public override { - require(_operator == msg.sender || msg.sender == address(this), "Only the current operator or the contract owner can set the operator."); - _operator = newOperator; - emit NewOperator(_operator); - } - - // Rate a customer - // Only the operator can call this function - function rate(address customer, int8 rating) public override { - require(_operator == msg.sender, "Only the operator can assign ratings."); - bytes32 hash = keccak256(abi.encodePacked(customer)); - _ratings[hash] = rating; - emit Rating(customer, rating); - } - - // Remove a rating from a customer - // Only the operator can call this function - function removeRating(address customer) external override { - require(_operator == msg.sender, "Only the operator can remove ratings."); - bytes32 hash = keccak256(abi.encodePacked(customer)); - delete _ratings[hash]; - emit Removal(customer); - } - - // Get the rating for a customer - function getOperator() public view returns (address) { - return _operator; - } - - // Check if a customer has been rated - function hasBeenRated(address customer) public view returns (bool) { - // Hash the customer address - bytes32 hash = keccak256(abi.encodePacked(customer)); - - // Check if the hash exists in the mapping - return _ratings[hash] != 0; - } - - function ratingOf(address _rated) public view override returns (int8) { - bytes32 hash = keccak256(abi.encodePacked(_rated)); - // Check if the customer has been rated - require(hasBeenRated(_rated), "This customer has not been rated yet."); - // Return the customer's rating - return _ratings[hash]; - } - - // Award ETH to a customer based on their rating - function awardEth(address payable customer) public payable { - // Calculate the amount of ETH to award based on the customer's rating - int8 rating = ratingOf(customer); - require(rating > 0, "Sorry, this customer has a rating less than 0 and cannot be awarded."); - uint256 award = uint256(int256(rating)); - // Transfer the ETH to the customer - require(address(this).balance >= award, "Contract has insufficient balance to award ETH."); - customer.transfer(award); - } - - receive () external payable {} - -} \ No newline at end of file diff --git a/assets/eip-4974/IERC4974.sol b/assets/eip-4974/IERC4974.sol deleted file mode 100644 index 8043c6e..0000000 --- a/assets/eip-4974/IERC4974.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/// @title EIP-4974 Ratings -/// @dev See https://eips.ethereum.org/EIPS/EIP-4974 -/// Note: the EIP-165 identifier for this interface is #######. -/// Must initialize contracts with an `operator` address that is not `address(0)`. -interface IERC4974 /* is ERC165 */ { - - /// @dev Emits when operator changes. - /// MUST emit when `operator` changes by any mechanism. - /// MUST ONLY emit by `setOperator`. - event NewOperator(address indexed _operator); - - /// @dev Emits when operator issues a rating. - /// MUST emit when rating is assigned by any mechanism. - /// MUST ONLY emit by `rate`. - event Rating(address _rated, int8 _rating); - - /// @dev Emits when operator removes a rating. - /// MUST emit when rating is removed by any mechanism. - /// MUST ONLY emit by `remove`. - event Removal(address _removed); - - /// @notice Appoint operator authority. - /// @dev MUST throw unless `msg.sender` is `operator`. - /// MUST throw if `operator` address is either already current `operator` - /// or is the zero address. - /// MUST emit an `Appointment` event. - /// @param _operator New operator of the smart contract. - function setOperator(address _operator) external; - - /// @notice Rate an address. - /// MUST emit a Rating event with each successful call. - /// @param _rated Address to be rated. - /// @param _rating Total EXP tokens to reallocate. - function rate(address _rated, int8 _rating) external; - - /// @notice Remove a rating from an address. - /// MUST emit a Remove event with each successful call. - /// @param _removed Address to be removed. - function removeRating(address _removed) external; - - /// @notice Return a rated address' rating. - /// @dev MUST register each time `Rating` emits. - /// SHOULD throw for queries about the zero address. - /// @param _rated An address for whom to query rating. - /// @return int8 The rating assigned. - function ratingOf(address _rated) external view returns (int8); -} - -interface IERC165 { - /// @notice Query if a contract implements an interface. - /// @dev Interface identification is specified in EIP-165. This function - /// uses less than 30,000 gas. - /// @param interfaceID The interface identifier, as specified in EIP-165. - /// @return bool `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise. - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-4974/IERC4974Metadata.sol b/assets/eip-4974/IERC4974Metadata.sol deleted file mode 100644 index fe75396..0000000 --- a/assets/eip-4974/IERC4974Metadata.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4974.sol"; - -/// @title ERC-4974 EXP Token Standard, optional metadata extension -/// @dev See https://eips.ethereum.org/EIPS/EIP-4974 -/// Note: the ERC-165 identifier for this interface is 0x74793a15. -interface IERC4974Metadata is IERC4974 { - /// @notice A descriptive name for the EXP in this contract. - function name() external view returns (string memory); - - /// @notice A one-line description of the EXP in this contract. - function description() external view returns (string memory); -} \ No newline at end of file diff --git a/assets/eip-4987/Consumer.sol b/assets/eip-4987/Consumer.sol deleted file mode 100644 index 77302c8..0000000 --- a/assets/eip-4987/Consumer.sol +++ /dev/null @@ -1,84 +0,0 @@ -/* -Consumer - -SPDX-License-Identifier: CC0-1.0 -*/ - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -import "./IERC721Holder.sol"; - -/** - * @title Consumer - * - * @notice this contract implements an example "consumer" of the proposed - * held token ERC standard. - - * This example consumer contract will query ERC721 ownership and balances - * including any "held" tokens - */ -contract Consumer { - using Address for address; - - // members - IERC721 public token; - - /** - * @param token_ address of ERC721 token - */ - constructor(address token_) { - token = IERC721(token_); - } - - /** - * @notice get the functional owner of a token - * @param tokenId token id of interest - */ - function getOwner(uint256 tokenId) external view returns (address) { - // get raw owner - address owner = token.ownerOf(tokenId); - - // if owner is not contract, return - if (!owner.isContract()) { - return owner; - } - - // check for token holder interface support - try IERC165(owner).supportsInterface(0x16b900ff) returns (bool ret) { - if (!ret) return owner; - } catch { - return owner; - } - - // check for held owner - try IERC721Holder(owner).heldOwnerOf(address(token), tokenId) returns (address user) { - if (user != address(0)) return user; - } catch {} - - return owner; - } - - /** - * @notice get the total user balance including held tokens - * @param owner user address - * @param holders list of token holder addresses - */ - function getBalance(address owner, address[] calldata holders) - external - view - returns (uint256) - { - // start with raw token balance - uint256 balance = token.balanceOf(owner); - - // consider each provided token holder contract - for (uint256 i = 0; i < holders.length; i++) { - balance += IERC721Holder(holders[i]).heldBalanceOf(address(token), owner); - } - - return balance; - } -} diff --git a/assets/eip-4987/IERC1155Holder.sol b/assets/eip-4987/IERC1155Holder.sol deleted file mode 100644 index d98daee..0000000 --- a/assets/eip-4987/IERC1155Holder.sol +++ /dev/null @@ -1,56 +0,0 @@ -/* -IERC1155Holder - -SPDX-License-Identifier: CC0-1.0 -*/ - -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -pragma solidity ^0.8.0; - -/** - * @notice the ERC1155 holder standard provides a common interface to query - * token balance information - */ -interface IERC1155Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - * @param tokenAmount held token amount - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 tokenAmount - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - * @param tokenAmount held token amount - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 tokenAmount - ); - - /** - * @notice get the held balance of the token owner - * @param tokenAddress held token address - * @param owner functional token owner - * @param tokenId held token ID - * @return held token balance - */ - function heldBalanceOf( - address tokenAddress, - address owner, - uint256 tokenId - ) external view returns (uint256); -} diff --git a/assets/eip-4987/IERC20Holder.sol b/assets/eip-4987/IERC20Holder.sol deleted file mode 100644 index fe040a2..0000000 --- a/assets/eip-4987/IERC20Holder.sol +++ /dev/null @@ -1,51 +0,0 @@ -/* -IERC20Holder - - -SPDX-License-Identifier: CC0-1.0 -*/ - -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -pragma solidity ^0.8.0; - -/** - * @notice the ERC20 holder standard provides a common interface to query - * token balance information - */ -interface IERC20Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenAmount held token amount - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 tokenAmount - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenAmount held token amount - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 tokenAmount - ); - - /** - * @notice get the held balance of the token owner - * @param tokenAddress held token address - * @param owner functional token owner - * @return held token balance - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - returns (uint256); -} diff --git a/assets/eip-4987/IERC721Holder.sol b/assets/eip-4987/IERC721Holder.sol deleted file mode 100644 index ed58be9..0000000 --- a/assets/eip-4987/IERC721Holder.sol +++ /dev/null @@ -1,61 +0,0 @@ -/* -IERC721Holder - -SPDX-License-Identifier: CC0-1.0 -*/ - -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -pragma solidity ^0.8.0; - -/** - * @notice the ERC721 holder standard provides a common interface to query - * token ownership and balance information - */ -interface IERC721Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId - ); - - /** - * @notice get the functional owner of a held token - * @param tokenAddress held token address - * @param tokenId held token ID - * @return functional token owner - */ - function heldOwnerOf(address tokenAddress, uint256 tokenId) - external - view - returns (address); - - /** - * @notice get the held balance of the token owner - * @param tokenAddress held token address - * @param owner functional token owner - * @return held token balance - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - returns (uint256); -} diff --git a/assets/eip-4987/Vault.sol b/assets/eip-4987/Vault.sol deleted file mode 100644 index 3045cd6..0000000 --- a/assets/eip-4987/Vault.sol +++ /dev/null @@ -1,125 +0,0 @@ -/* -Vault - -SPDX-License-Identifier: CC0-1.0 -*/ - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -import "./IERC721Holder.sol"; - -/** - * @title Vault - * - * @notice this contract implements an example "holder" for the proposed - * held token ERC standard. - - * This example vault contract allows a user to lock up an ERC721 token for - * a specified period of time, while still reporting the functional owner - */ -contract Vault is ERC165, IERC721Holder { - // members - IERC721 public token; - uint256 public timelock; - mapping(uint256 => address) public owners; - mapping(uint256 => uint256) public locks; - mapping(address => uint256) public balances; - - /** - * @param token_ address of token to be stored in vault - * @param timelock_ duration in seconds that tokens will be locked - */ - constructor(address token_, uint256 timelock_) { - token = IERC721(token_); - timelock = timelock_; - } - - /** - * @inheritdoc IERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC165, IERC165) - returns (bool) - { - return - interfaceId == type(IERC721Holder).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @inheritdoc IERC721Holder - */ - function heldOwnerOf(address tokenAddress, uint256 tokenId) - external - view - override - returns (address) - { - require( - tokenAddress == address(token), - "ERC721Vault: invalid token address" - ); - return owners[tokenId]; - } - - /** - * @inheritdoc IERC721Holder - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - override - returns (uint256) - { - require( - tokenAddress == address(token), - "ERC721Vault: invalid token address" - ); - return balances[owner]; - } - - /** - * @notice deposit and lock a token for a period of time - * @param tokenId ID of token to deposit - */ - function deposit(uint256 tokenId) public { - require( - msg.sender == token.ownerOf(tokenId), - "ERC721Vault: sender does not own token" - ); - - owners[tokenId] = msg.sender; - locks[tokenId] = block.timestamp + timelock; - balances[msg.sender]++; - - emit Hold(msg.sender, address(token), tokenId); - - token.transferFrom(msg.sender, address(this), tokenId); - } - - /** - * @notice withdraw token after timelock has elapsed - * @param tokenId ID of token to withdraw - */ - function withdraw(uint256 tokenId) public { - require( - msg.sender == owners[tokenId], - "ERC721Vault: sender does not own token" - ); - require(block.timestamp > locks[tokenId], "ERC721Vault: token is locked"); - - delete owners[tokenId]; - delete locks[tokenId]; - balances[msg.sender]--; - - emit Release(msg.sender, address(token), tokenId); - - token.safeTransferFrom(address(this), msg.sender, tokenId); - } -} diff --git a/assets/eip-5006/.gitignore b/assets/eip-5006/.gitignore deleted file mode 100644 index e0da618..0000000 --- a/assets/eip-5006/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -package-lock.json -typechain/ -cache/ -artifacts/ \ No newline at end of file diff --git a/assets/eip-5006/contracts/ERC5006.sol b/assets/eip-5006/contracts/ERC5006.sol deleted file mode 100644 index 1747438..0000000 --- a/assets/eip-5006/contracts/ERC5006.sol +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Receiver.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "./IERC5006.sol"; - -contract ERC5006 is ERC1155, ERC1155Receiver, IERC5006 { - using EnumerableSet for EnumerableSet.UintSet; - mapping(uint256 => mapping(address => uint256)) private _frozens; - mapping(uint256 => UserRecord) private _records; - mapping(uint256 => mapping(address => EnumerableSet.UintSet)) - private _userRecordIds; - uint256 _curRecordId; - uint256 recordLimit; - - constructor(string memory uri_, uint256 recordLimit_) ERC1155(uri_) { - recordLimit = recordLimit_; - } - - function isOwnerOrApproved(address owner) public view returns (bool) { - require( - owner == msg.sender || isApprovedForAll(owner, msg.sender), - "only owner or approved" - ); - return true; - } - - function usableBalanceOf(address account, uint256 tokenId) - public - view - override - returns (uint256 amount) - { - uint256[] memory recordIds = _userRecordIds[tokenId][account].values(); - for (uint256 i = 0; i < recordIds.length; i++) { - if (block.timestamp <= _records[recordIds[i]].expiry) { - amount += _records[recordIds[i]].amount; - } - } - } - - function frozenBalanceOf(address account, uint256 tokenId) - public - view - override - returns (uint256) - { - return _frozens[tokenId][account]; - } - - function userRecordOf(uint256 recordId) - public - view - override - returns (UserRecord memory) - { - return _records[recordId]; - } - - function createUserRecord( - address owner, - address user, - uint256 tokenId, - uint64 amount, - uint64 expiry - ) public override returns (uint256) { - require(isOwnerOrApproved(owner)); - require(user != address(0), "user cannot be the zero address"); - require(amount > 0, "amount must be greater than 0"); - require(expiry > block.timestamp, "expiry must after the block timestamp"); - require( - _userRecordIds[tokenId][user].length() < recordLimit, - "user cannot have more records" - ); - _safeTransferFrom(owner, address(this), tokenId, amount, ""); - _frozens[tokenId][owner] += amount; - _curRecordId++; - _records[_curRecordId] = UserRecord( - tokenId, - owner, - amount, - user, - expiry - ); - _userRecordIds[tokenId][user].add(_curRecordId); - emit CreateUserRecord( - _curRecordId, - tokenId, - amount, - owner, - user, - expiry - ); - return _curRecordId; - } - - function deleteUserRecord(uint256 recordId) public override { - UserRecord storage _record = _records[recordId]; - require(isOwnerOrApproved(_record.owner)); - _safeTransferFrom( - address(this), - _record.owner, - _record.tokenId, - _record.amount, - "" - ); - _frozens[_record.tokenId][_record.owner] -= _record.amount; - _userRecordIds[_record.tokenId][_record.user].remove(recordId); - delete _records[recordId]; - emit DeleteUserRecord(recordId); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(ERC1155, ERC1155Receiver) - returns (bool) - { - return - interfaceId == type(IERC5006).interfaceId || - ERC1155.supportsInterface(interfaceId) || - ERC1155Receiver.supportsInterface(interfaceId); - } - - function onERC1155Received( - address operator, - address from, - uint256 tokenId, - uint256 value, - bytes calldata data - ) external pure override returns (bytes4) { - return IERC1155Receiver.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external pure override returns (bytes4) { - return IERC1155Receiver.onERC1155BatchReceived.selector; - } -} diff --git a/assets/eip-5006/contracts/ERC5006Demo.sol b/assets/eip-5006/contracts/ERC5006Demo.sol deleted file mode 100644 index fd409e0..0000000 --- a/assets/eip-5006/contracts/ERC5006Demo.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5006.sol"; - -contract ERC5006Demo is ERC5006 { - constructor(string memory uri_, uint256 recordLimit_) - ERC5006(uri_, recordLimit_) - {} - - function mint( - address to, - uint256 id, - uint256 amount - ) public { - _mint(to, id, amount, ""); - } - - function burn( - address from, - uint256 id, - uint256 amount - ) public { - _burn(from, id, amount); - } - - function getInterfaceId() public view returns (bytes4) { - return type(IERC5006).interfaceId; - } -} diff --git a/assets/eip-5006/contracts/IERC5006.sol b/assets/eip-5006/contracts/IERC5006.sol deleted file mode 100644 index 4d7a147..0000000 --- a/assets/eip-5006/contracts/IERC5006.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5006 { - struct UserRecord { - uint256 tokenId; - address owner; - uint64 amount; - address user; - uint64 expiry; - } - /** - * @dev Emitted when permission (for `user` to use `amount` of `tokenId` token owned by `owner` - * until `expiry`) is given. - */ - event CreateUserRecord( - uint256 recordId, - uint256 tokenId, - uint64 amount, - address owner, - address user, - uint64 expiry - ); - /** - * @dev Emitted when record of `recordId` is deleted. - */ - event DeleteUserRecord(uint256 recordId); - - /** - * @dev Returns the usable amount of `tokenId` tokens by `account`. - */ - function usableBalanceOf(address account, uint256 tokenId) - external - view - returns (uint256); - - /** - * @dev Returns the amount of frozen tokens of token type `id` by `account`. - */ - function frozenBalanceOf(address account, uint256 tokenId) - external - view - returns (uint256); - - /** - * @dev Returns the `UserRecord` of `recordId`. - */ - function userRecordOf(uint256 recordId) - external - view - returns (UserRecord memory); - - /** - * @dev Gives permission to `user` to use `amount` of `tokenId` token owned by `owner` until `expiry`. - * - * Emits a {CreateUserRecord} event. - * - * Requirements: - * - * - If the caller is not `owner`, it must be have been approved to spend ``owner``'s tokens - * via {setApprovalForAll}. - * - `owner` must have a balance of tokens of type `id` of at least `amount`. - * - `user` cannot be the zero address. - * - `amount` must be greater than 0. - * - `expiry` must after the block timestamp. - */ - function createUserRecord( - address owner, - address user, - uint256 tokenId, - uint64 amount, - uint64 expiry - ) external returns (uint256); - - /** - * @dev Atomically delete `record` of `recordId` by the caller. - * - * Emits a {DeleteUserRecord} event. - * - * Requirements: - * - * - the caller must have allowance. - */ - function deleteUserRecord(uint256 recordId) external; -} diff --git a/assets/eip-5006/hardhat.config.ts b/assets/eip-5006/hardhat.config.ts deleted file mode 100644 index 9b10629..0000000 --- a/assets/eip-5006/hardhat.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HardhatUserConfig, task } from "hardhat/config"; -import "@nomiclabs/hardhat-waffle"; -import "@typechain/hardhat"; - -// This is a sample Hardhat task. To learn how to create your own go to -// https://hardhat.org/guides/create-task.html -task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { - const accounts = await hre.ethers.getSigners(); - - for (const account of accounts) { - console.log(account.address); - } -}); - - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.9", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - }, - -}; - - - -export default config; diff --git a/assets/eip-5006/package.json b/assets/eip-5006/package.json deleted file mode 100644 index a20ad3f..0000000 --- a/assets/eip-5006/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "EIP-5006", - "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.5", - "@nomiclabs/hardhat-waffle": "^2.0.3", - "@openzeppelin/contracts": "^4.5.0", - "@typechain/ethers-v5": "^7.2.0", - "@typechain/hardhat": "^2.3.1", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "@types/node": "^12.20.47", - "chai": "^4.3.6", - "ethers": "^5.6.1", - "hardhat": "^2.9.2", - "solhint": "^3.3.7", - "ts-node": "^10.8.1", - "typechain": "^5.2.0", - "typescript": "^4.6.3" - } -} diff --git a/assets/eip-5006/test/test.ts b/assets/eip-5006/test/test.ts deleted file mode 100644 index 41fd7bd..0000000 --- a/assets/eip-5006/test/test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "hardhat"; -import hre from "hardhat"; - -describe("Test 1155 User Role", function () { - let alice, bob, carl; - let contract; - let expiry; - - async function checkRecord(rid,tokenId,amount,owner,user,expiry_) { - let record = await contract.userRecordOf(rid); - expect(record[0]).equals(tokenId,"tokenId"); - expect(record[1]).equals(owner,"owner"); - expect(record[2]).equals(amount,"amount"); - expect(record[3]).equals(user,"user"); - expect(record[4]).equals(expiry_,"expiry_"); - } - - beforeEach(async function () { - [alice, bob, carl] = await ethers.getSigners(); - - const ERC5006Demo = await ethers.getContractFactory("ERC5006Demo"); - - contract = await ERC5006Demo.deploy("", 3); - - expiry = Math.floor(new Date().getTime() / 1000) + 3600; - }); - - - - describe("", function () { - - it("InterfaceId should equals 0xc26d96cc", async function () { - expect(await contract.getInterfaceId()).equals("0xc26d96cc"); - }); - - it("Should set user to bob success", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - - await checkRecord(1,1,10,alice.address,bob.address,expiry); - - expect(await contract.usableBalanceOf(bob.address, 1)).equals(10); - - expect(await contract.balanceOf(alice.address, 1)).equals(90); - - expect(await contract.frozenBalanceOf(alice.address, 1)).equals(10); - - }); - - it("Should set user to bob fail", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - await expect(contract.createUserRecord(alice.address, bob.address, 1, 10, expiry)).to.be.revertedWith("user cannot have more records"); - - }); - - it("Should set user to bob fail : balance is not enough", async function () { - - await contract.mint(alice.address, 1, 100); - - await expect(contract.createUserRecord(alice.address, bob.address, 1, 101, expiry)).to.be.revertedWith('ERC1155: insufficient balance for transfer'); - - }); - - it("Should set user to bob fail : only owner or approved", async function () { - - await contract.mint(alice.address, 1, 100); - await contract.mint(carl.address, 1, 100); - - await expect(contract.createUserRecord(carl.address, bob.address, 1, 110, expiry)).to.be.revertedWith('only owner or approved'); - - }); - - it("Should deleteUserRecord success", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - - // await hre.network.provider.send("hardhat_mine", ["0x5a0", "0x3c"]); - - await contract.deleteUserRecord(1); - - await checkRecord(1,0,0,"0x0000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000",0); - - expect(await contract.usableBalanceOf(bob.address, 1)).equals(0); - - expect(await contract.balanceOf(alice.address, 1)).equals(100); - - expect(await contract.frozenBalanceOf(alice.address, 1)).equals(0); - - }); - - - it("bob should deleteUserRecord fail", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - - await expect(contract.connect(bob).deleteUserRecord(1)).to.be.revertedWith("only owner or approved"); - - }); - - - }); - - -}); diff --git a/assets/eip-5006/tsconfig.json b/assets/eip-5006/tsconfig.json deleted file mode 100644 index c458030..0000000 --- a/assets/eip-5006/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": false, - "esModuleInterop": true, - "outDir": "dist", - "declaration": true - }, - "include": ["./test", "./typechain"], - "files": ["./hardhat.config.ts"] -} diff --git a/assets/eip-5007/.gitignore b/assets/eip-5007/.gitignore deleted file mode 100644 index d5f19d8..0000000 --- a/assets/eip-5007/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -package-lock.json diff --git a/assets/eip-5007/README.md b/assets/eip-5007/README.md deleted file mode 100644 index 9bc3c31..0000000 --- a/assets/eip-5007/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# EIP-5007 -This standard is an extension of [ERC-721](../../EIPS/eip-721.md). It proposes some additional functions (`startTime`, `endTime`) to help with on-chain time management. - -## Tools -* [Truffle](https://truffleframework.com/) - a development framework for Ethereum - -## Install -``` -npm install -``` - -## Test -``` -truffle test -``` diff --git a/assets/eip-5007/contracts/ERC5007.sol b/assets/eip-5007/contracts/ERC5007.sol deleted file mode 100644 index fecedb5..0000000 --- a/assets/eip-5007/contracts/ERC5007.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5007.sol"; - -abstract contract ERC5007 is ERC721, IERC5007 { - struct TimeNftInfo { - int64 startTime; - int64 endTime; - } - - mapping(uint256 => TimeNftInfo) internal _timeNftMapping; - - /** - * @dev See {IERC5007-startTime}. - */ - function startTime(uint256 tokenId) - public - view - virtual - override - returns (int64) { - require(_exists(tokenId), "ERC5007: invalid tokenId"); - return _timeNftMapping[tokenId].startTime; - } - - /** - * @dev See {IERC5007-endTime}. - */ - function endTime(uint256 tokenId) - public - view - virtual - override - returns (int64) { - require(_exists(tokenId), "ERC5007: invalid tokenId"); - return _timeNftMapping[tokenId].endTime; - } - - /** - * @dev mint a new time NFT. - * - * Requirements: - * - * - `tokenId_` must not exist. - * - `to_` cannot be the zero address. - * - `endTime_` should be equal or greater than `startTime_` - */ - function _mintTimeNft( - address to_, - uint256 tokenId_, - int64 startTime_, - int64 endTime_ - ) internal virtual { - require(endTime_ >= startTime_, 'ERC5007: invalid endTime'); - _mint(to_, tokenId_); - TimeNftInfo storage info = _timeNftMapping[tokenId_]; - info.startTime = startTime_; - info.endTime = endTime_; - } - - - /** - * @dev Destroys `tokenId`. - * - * Requirements: - * - * - `tokenId` must exist. - * - */ - function _burn(uint256 tokenId) internal virtual override { - super._burn(tokenId); - delete _timeNftMapping[tokenId]; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) { - return - interfaceId == type(IERC5007).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5007/contracts/ERC5007Composable.sol b/assets/eip-5007/contracts/ERC5007Composable.sol deleted file mode 100644 index e27defe..0000000 --- a/assets/eip-5007/contracts/ERC5007Composable.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./ERC5007.sol"; -import "./IERC5007Composable.sol"; - -abstract contract ERC5007Composable is ERC5007, IERC5007Composable { - mapping(uint256 => uint256) internal _rootIdMapping; - - /** - * @dev See {IERC5007Composable-rootTokenId}. - */ - function rootTokenId(uint256 tokenId) - public - view - override - returns (uint256 rootId) - { - require(_exists(tokenId), "ERC5007: invalid tokenId"); - rootId = _rootIdMapping[tokenId]; - } - - /** - * @dev See {IERC5007Composable-split}. - */ - function split( - uint256 oldTokenId, - uint256 newTokenId, - address newTokenOwner, - int64 newTokenStartTime - ) public virtual override { - require( - _isApprovedOrOwner(_msgSender(), oldTokenId), - "ERC5007: caller is not owner nor approved" - ); - - int64 oldTokenStartTime = _timeNftMapping[oldTokenId].startTime; - int64 oldTokenEndTime = _timeNftMapping[oldTokenId].endTime; - require( - oldTokenStartTime < newTokenStartTime && - newTokenStartTime <= oldTokenEndTime, - "ERC5007: invalid newTokenStartTime" - ); - - _timeNftMapping[oldTokenId].endTime = newTokenStartTime - 1; - int64 newTokenEndTime = oldTokenEndTime; - - _mintTimeNftWithRootId( - newTokenOwner, - newTokenId, - _rootIdMapping[oldTokenId], - newTokenStartTime, - newTokenEndTime - ); - } - - /** - * @dev See {IERC5007Composable-merge}. - */ - function merge( - uint256 firstTokenId, - uint256 secondTokenId, - address newTokenOwner, - uint256 newTokenId - ) public virtual { - require( - _isApprovedOrOwner(_msgSender(), firstTokenId) && - _isApprovedOrOwner(_msgSender(), secondTokenId), - "ERC5007: caller is not owner nor approved" - ); - - TimeNftInfo memory firstToken = _timeNftMapping[firstTokenId]; - TimeNftInfo memory secondToken = _timeNftMapping[secondTokenId]; - require( - _rootIdMapping[firstTokenId] == _rootIdMapping[secondTokenId] && - firstToken.startTime <= firstToken.endTime && - (firstToken.endTime + 1) == secondToken.startTime && - secondToken.startTime <= secondToken.endTime, - "ERC5007: invalid input data" - ); - - - _mintTimeNftWithRootId( - newTokenOwner, - newTokenId, - _rootIdMapping[firstTokenId], - firstToken.startTime, - secondToken.endTime - ); - - _burn(firstTokenId); - _burn(secondTokenId); - } - - /** - * @dev mint a new common time NFT - * - * Requirements: - * - * - `to_` cannot be the zero address. - * - `tokenId_` must not exist. - * - `rootId_` must exist. - * - `endTime_` should be equal or greater than `startTime_` - */ - function _mintTimeNftWithRootId( - address to_, - uint256 tokenId_, - uint256 rootId_, - int64 startTime_, - int64 endTime_ - ) internal virtual { - require(_exists(rootId_), "ERC5007: invalid rootId_"); - super._mintTimeNft(to_, tokenId_, startTime_, endTime_); - _rootIdMapping[tokenId_] = rootId_; - } - - /** - * @dev mint a new common time NFT - * - * Requirements: - * - * - `to_` cannot be the zero address. - * - `tokenId_` must not exist. - * - `endTime_` should be equal or greater than `startTime_` - */ - function _mintTimeNft( - address to_, - uint256 tokenId_, - int64 startTime_, - int64 endTime_ - ) internal virtual override { - super._mintTimeNft(to_, tokenId_, startTime_, endTime_); - - _rootIdMapping[tokenId_] = tokenId_; - } - - /** - * @dev Destroys `tokenId`. - * - * Requirements: - * - * - `tokenId` must exist. - * - */ - function _burn(uint256 tokenId) internal virtual override { - super._burn(tokenId); - delete _rootIdMapping[tokenId]; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5007Composable).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5007/contracts/ERC5007ComposableTest.sol b/assets/eip-5007/contracts/ERC5007ComposableTest.sol deleted file mode 100644 index 7524ba2..0000000 --- a/assets/eip-5007/contracts/ERC5007ComposableTest.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5007Composable.sol"; - -contract ERC5007ComposableTest is ERC5007Composable { - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - /// @notice mint a new root time NFT - /// @param to_ The owner of the new token - /// @param id_ The id of the new token - /// @param startTime_ The start time of the new token - /// @param endTime_ The end time of the new token - function mint( - address to_, - uint256 id_, - int64 startTime_, - int64 endTime_ - ) public { - super._mintTimeNft(to_, id_, startTime_, endTime_); - } - - /** - * @dev Returns the interfaceId of IERC5007Composable. - */ - function getInterfaceId() public pure returns (bytes4) { - return type(IERC5007Composable).interfaceId; - } -} diff --git a/assets/eip-5007/contracts/ERC5007Demo.sol b/assets/eip-5007/contracts/ERC5007Demo.sol deleted file mode 100644 index 67ee031..0000000 --- a/assets/eip-5007/contracts/ERC5007Demo.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5007.sol"; - -contract ERC5007Demo is ERC5007 { - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){} - - /** - * @dev mint a new time NFT - * - * Requirements: - * - * - `to_` cannot be the zero address. - * - `tokenId_` must not exist. - * - `endTime_` should be equal or greater than `startTime_` - */ - function mint( - address to_, - uint256 tokenId_, - int64 startTime_, - int64 endTime_ - ) public { - _mintTimeNft(to_, tokenId_, startTime_, endTime_); - } - - /** - * @dev Returns the interfaceId of IERC5007. - */ - function getInterfaceId() public pure returns (bytes4) { - return type(IERC5007).interfaceId; - } -} diff --git a/assets/eip-5007/contracts/IERC5007.sol b/assets/eip-5007/contracts/IERC5007.sol deleted file mode 100644 index 9e5f6d6..0000000 --- a/assets/eip-5007/contracts/IERC5007.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5007 /* is IERC1155 */ { - /** - * @dev Returns the start time of the NFT. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function startTime(uint256 tokenId) external view returns (int64); - - /** - * @dev Returns the end time of the NFT. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function endTime(uint256 tokenId) external view returns (int64); -} diff --git a/assets/eip-5007/contracts/IERC5007Composable.sol b/assets/eip-5007/contracts/IERC5007Composable.sol deleted file mode 100644 index f1c58c5..0000000 --- a/assets/eip-5007/contracts/IERC5007Composable.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - - -interface IERC5007Composable /* is IERC5007 */ { - /** - * @dev Returns the ancestor token id of the NFT. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function rootTokenId(uint256 tokenId) external view returns (uint256); - - /** - * @dev Mint a new token from an old token. - * The rootTokenId of the new token is the same as the rootTokenId of the old token - * - * Requirements: - * - * - `oldTokenId` must exist. - * - `newTokenId` must not exist. - * - `newTokenOwner` cannot be the zero address. - * - `newTokenStartTime` require(oldTokenStartTime < newTokenStartTime && newTokenStartTime <= oldTokenEndTime) - */ - function split( - uint256 oldTokenId, - uint256 newTokenId, - address newTokenOwner, - int64 newTokenStartTime - ) external; - - /** - * @dev Merge the first token and second token into the new token. - * - * Requirements: - * - * - `firstTokenId` must exist. - * - `secondTokenId` must exist. require((firstToken.endTime + 1) == secondToken.startTime) - * - `newTokenOwner` cannot be the zero address. - * - `newTokenId` must not exist. - */ - function merge( - uint256 firstTokenId, - uint256 secondTokenId, - address newTokenOwner, - uint256 newTokenId - ) external; -} diff --git a/assets/eip-5007/migrations/1_initial_migration.js b/assets/eip-5007/migrations/1_initial_migration.js deleted file mode 100644 index 31c03a3..0000000 --- a/assets/eip-5007/migrations/1_initial_migration.js +++ /dev/null @@ -1,7 +0,0 @@ -const ERC5007Demo = artifacts.require("ERC5007Demo"); -const ERC5007ComposableTest = artifacts.require("ERC5007ComposableTest"); - -module.exports = function (deployer) { - deployer.deploy(ERC5007Demo,'ERC5007Demo','ERC5007Demo'); - deployer.deploy(ERC5007ComposableTest,'ERC5007ComposableTest','ERC5007ComposableTest'); -}; diff --git a/assets/eip-5007/package.json b/assets/eip-5007/package.json deleted file mode 100644 index eb917b7..0000000 --- a/assets/eip-5007/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "ERC5007", - "dependencies": { - "@openzeppelin/contracts": "^4.3.3", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "bignumber.js": "^9.0.1", - "chai": "^4.3.6" - } -} diff --git a/assets/eip-5007/test/test.js b/assets/eip-5007/test/test.js deleted file mode 100644 index ecb87af..0000000 --- a/assets/eip-5007/test/test.js +++ /dev/null @@ -1,95 +0,0 @@ -const { assert } = require("chai"); - -const { BigNumber } = require("bignumber.js") - -const ERC5007Demo = artifacts.require("ERC5007Demo"); -const ERC5007ComposableTest = artifacts.require("ERC5007ComposableTest"); - -contract("test ERC5007", async accounts => { - - it("test ERC5007", async () => { - const Alice = accounts[0]; - - const instance = await ERC5007Demo.deployed("ERC5007Demo", "ERC5007Demo"); - const demo = instance; - - let now = Math.floor(new Date().getTime()/1000); - let inputStartTime1 = new BigNumber(now - 10000); - let inputEndTime1 = new BigNumber(now + 10000); - let id1 = 1; - - await demo.mint(Alice, id1, inputStartTime1.toFixed(0), inputEndTime1.toFixed(0)); - - - let outputStartTime1 = await demo.startTime(id1); - let outputEndTime1 = await demo.endTime(id1); - assert.equal(inputStartTime1.comparedTo(outputStartTime1) == 0 && inputEndTime1.comparedTo(outputEndTime1) == 0, true, "wrong data"); - - - console.log("IERC5007 InterfaceId:", await demo.getInterfaceId()) - let isSupport = await demo.supportsInterface('0x7a0cdf92'); - assert.equal(isSupport, true , "supportsInterface error"); - - }); - - it("test ERC5007Composable", async () => { - const Alice = accounts[0]; - const Bob = accounts[1]; - const Carl = accounts[2]; - - const instance = await ERC5007ComposableTest.deployed("ERC5007ComposableTest", "ERC5007ComposableTest"); - const demo = instance; - - let now = Math.floor(new Date().getTime()/1000); - let token1InputStartTime = new BigNumber(now - 10000); - let token1InputEndTime = new BigNumber(now + 10000); - let id1 = 1; - - await demo.mint(Alice, id1, token1InputStartTime.toFixed(0), token1InputEndTime.toFixed(0)); - - let token1OutputStartTime = new BigNumber( await demo.startTime(id1)); - let token1OutputEndTime = new BigNumber( await demo.endTime(id1)); - assert.equal(token1InputStartTime.comparedTo(token1OutputStartTime) == 0 - && token1InputEndTime.comparedTo(token1OutputEndTime) == 0, true, "wrong data"); - - let id2 = 2; - let token2InputStartTime = token1InputStartTime.plus(5000); - await demo.split(id1, id2, Bob, token2InputStartTime.toFixed(0)); - - token1OutputStartTime = new BigNumber( await demo.startTime(id1)); - token1OutputEndTime = new BigNumber( await demo.endTime(id1)); - - let token2OutputStartTime = new BigNumber( await demo.startTime(id2)); - let token2OutputEndTime = new BigNumber( await demo.endTime(id2)); - - assert.equal(token1InputStartTime.comparedTo(token1OutputStartTime) == 0 - && token1OutputEndTime.comparedTo(token2InputStartTime.minus(1)) == 0, true, "wrong data"); - - assert.equal(token2InputStartTime.comparedTo(token2OutputStartTime) == 0 - && token2OutputEndTime.comparedTo(token1InputEndTime) == 0, true, "wrong data"); - - let token1RootId = await demo.rootTokenId(id1); - let token2RootId = await demo.rootTokenId(id2); - assert.equal(token1RootId == id1 && token2RootId == id1, true, 'wrong data'); - - let id3 = 3; - await demo.setApprovalForAll(Alice, true,{from: Bob}); - await demo.merge(id1, id2, Carl, id3); - - let token3OutputStartTime = new BigNumber( await demo.startTime(id3)); - let token3OutputEndTime = new BigNumber( await demo.endTime(id3)); - let token3RootId = await demo.rootTokenId(id3); - let token3Owner = await demo.ownerOf(id3); - - assert.equal(token1InputStartTime.comparedTo(token3OutputStartTime) == 0 - && token3OutputEndTime.comparedTo(token1InputEndTime) == 0, true, "wrong start time or end time"); - - assert.equal(token3RootId == id1, true, 'wrong rootId'); - assert.equal(token3Owner == Carl, true, 'wrong owner'); - - console.log("IERC5007Composable InterfaceId:", await demo.getInterfaceId()) - let isSupport = await demo.supportsInterface('0x620063db'); - assert.equal(isSupport, true , "supportsInterface error"); - }); - -}); diff --git a/assets/eip-5007/truffle-config.js b/assets/eip-5007/truffle-config.js deleted file mode 100644 index ccc194a..0000000 --- a/assets/eip-5007/truffle-config.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Use this file to configure your truffle project. It's seeded with some - * common settings for different networks and features like migrations, - * compilation and testing. Uncomment the ones you need or modify - * them to suit your project as necessary. - * - * More information about configuration can be found at: - * - * trufflesuite.com/docs/advanced/configuration - * - * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) - * to sign your transactions before they're sent to a remote public node. Infura accounts - * are available for free at: infura.io/register. - * - * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate - * public/private key pairs. If you're publishing your code to GitHub make sure you load this - * phrase from a file you've .gitignored so it doesn't accidentally become public. - * - */ - -// const HDWalletProvider = require('@truffle/hdwallet-provider'); -// -// const fs = require('fs'); -// const mnemonic = fs.readFileSync(".secret").toString().trim(); - -module.exports = { - /** - * Networks define how you connect to your ethereum client and let you set the - * defaults web3 uses to send transactions. If you don't specify one truffle - * will spin up a development blockchain for you on port 9545 when you - * run `develop` or `test`. You can ask a truffle command to use a specific - * network from the command line, e.g - * - * $ truffle test --network - */ - - networks: { - // Useful for testing. The `development` name is special - truffle uses it by default - // if it's defined here and no other network is specified at the command line. - // You should run a client (like ganache-cli, geth or parity) in a separate terminal - // tab if you use this network and you must also set the `host`, `port` and `network_id` - // options below to some value. - // - // development: { - // host: "127.0.0.1", // Localhost (default: none) - // port: 8545, // Standard Ethereum port (default: none) - // network_id: "*", // Any network (default: none) - // }, - // Another network with more advanced options... - // advanced: { - // port: 8777, // Custom port - // network_id: 1342, // Custom network - // gas: 8500000, // Gas sent with each transaction (default: ~6700000) - // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) - // from:
, // Account to send txs from (default: accounts[0]) - // websocket: true // Enable EventEmitter interface for web3 (default: false) - // }, - // Useful for deploying to a public network. - // NB: It's important to wrap the provider as a function. - // ropsten: { - // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), - // network_id: 3, // Ropsten's id - // gas: 5500000, // Ropsten has a lower block limit than mainnet - // confirmations: 2, // # of confs to wait between deployments. (default: 0) - // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) - // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) - // }, - // Useful for private networks - // private: { - // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), - // network_id: 2111, // This network is yours, in the cloud. - // production: true // Treats this network as if it was a public net. (default: false) - // } - }, - - // Set default mocha options here, use special reporters etc. - mocha: { - // timeout: 100000 - }, - - // Configure your compilers - compilers: { - solc: { - version: "0.8.10", // Fetch exact version from solc-bin (default: truffle's version) - // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) - settings: { // See the solidity docs for advice about optimization and evmVersion - optimizer: { - enabled: false, - runs: 200 - } - // , - // evmVersion: "byzantium" - // } - } - }, - - // Truffle DB is currently disabled by default; to enable it, change enabled: - // false to enabled: true. The default storage location can also be - // overridden by specifying the adapter settings, as shown in the commented code below. - // - // NOTE: It is not possible to migrate your contracts to truffle DB and you should - // make a backup of your artifacts to a safe location before enabling this feature. - // - // After you backed up your artifacts you can utilize db by running migrate as follows: - // $ truffle migrate --reset --compile-all - // - // db: { - // enabled: false, - // host: "127.0.0.1", - // adapter: { - // name: "sqlite", - // settings: { - // directory: ".db" - // } - // } - } -}; diff --git a/assets/eip-5008/.gitignore b/assets/eip-5008/.gitignore deleted file mode 100644 index b55321b..0000000 --- a/assets/eip-5008/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -package-lock.json -typechain/ -cache/ -artifacts/ diff --git a/assets/eip-5008/contracts/ERC5008.sol b/assets/eip-5008/contracts/ERC5008.sol deleted file mode 100644 index ccf251c..0000000 --- a/assets/eip-5008/contracts/ERC5008.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5008.sol"; - -contract ERC5008 is ERC721, IERC5008 { - mapping(uint256 => uint256) private _tokenNonce; - - constructor(string memory name_, string memory symbol_)ERC721(name_, symbol_){ - } - - /// @notice Get the nonce of an NFT - /// Throws if `tokenId` is not a valid NFT - /// @param tokenId The NFT to get the nonce for - /// @return The nonce of this NFT - function nonce(uint256 tokenId) public virtual override view returns(uint256) { - require(_exists(tokenId), "Error: query for nonexistent token"); - - return _tokenNonce[tokenId]; - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId); - _tokenNonce[tokenId]++; - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC5008).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5008/contracts/ERC5008Demo.sol b/assets/eip-5008/contracts/ERC5008Demo.sol deleted file mode 100644 index 0e1c6fb..0000000 --- a/assets/eip-5008/contracts/ERC5008Demo.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5008.sol"; - -contract ERC5008Demo is ERC5008{ - - constructor(string memory name_, string memory symbol_)ERC5008(name_, symbol_){ - } - - /// @notice mint a new NFT - /// @param to The owner of the new token - /// @param tokenId The id of the new token - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5008/contracts/IERC5008.sol b/assets/eip-5008/contracts/IERC5008.sol deleted file mode 100644 index 2dff84c..0000000 --- a/assets/eip-5008/contracts/IERC5008.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5008 /* is IERC165 */ { - /// @notice Get the nonce of an NFT - /// Throws if `tokenId` is not a valid NFT - /// @param tokenId The id of the NFT - /// @return The nonce of the NFT - function nonce(uint256 tokenId) external view returns(uint256); -} diff --git a/assets/eip-5008/hardhat.config.ts b/assets/eip-5008/hardhat.config.ts deleted file mode 100644 index 9b10629..0000000 --- a/assets/eip-5008/hardhat.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HardhatUserConfig, task } from "hardhat/config"; -import "@nomiclabs/hardhat-waffle"; -import "@typechain/hardhat"; - -// This is a sample Hardhat task. To learn how to create your own go to -// https://hardhat.org/guides/create-task.html -task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { - const accounts = await hre.ethers.getSigners(); - - for (const account of accounts) { - console.log(account.address); - } -}); - - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.9", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - }, - -}; - - - -export default config; diff --git a/assets/eip-5008/package.json b/assets/eip-5008/package.json deleted file mode 100644 index 4bdee48..0000000 --- a/assets/eip-5008/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "EIP-5008", - "dependencies": { - "@openzeppelin/contracts": "^4.7.3", - "@nomiclabs/hardhat-ethers": "^2.0.5", - "@nomiclabs/hardhat-waffle": "^2.0.3", - "@typechain/ethers-v5": "^7.2.0", - "@typechain/hardhat": "^2.3.1", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "@types/node": "^12.20.47", - "chai": "^4.3.6", - "ethers": "^5.6.1", - "hardhat": "^2.9.2", - "solhint": "^3.3.7", - "ts-node": "^10.8.1", - "typechain": "^5.2.0", - "typescript": "^4.6.3" - } -} diff --git a/assets/eip-5008/test/test.ts b/assets/eip-5008/test/test.ts deleted file mode 100644 index 22f272c..0000000 --- a/assets/eip-5008/test/test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("Test ERC5008 ", function () { - - it("test nonce", async function () { - let [alice, bob] = await ethers.getSigners(); - - const ERC5008Demo = await ethers.getContractFactory("ERC5008Demo"); - - let contract = await ERC5008Demo.deploy("ERC5008Demo","ERC5008Demo"); - - let tokenId = 1; - await contract.mint(alice.address, tokenId); - - expect(await contract.nonce(tokenId)).equals(1); - - await contract.transferFrom(alice.address, bob.address, tokenId); - - expect(await contract.nonce(tokenId)).equals(2); - }); -}); diff --git a/assets/eip-5008/tsconfig.json b/assets/eip-5008/tsconfig.json deleted file mode 100644 index c458030..0000000 --- a/assets/eip-5008/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": false, - "esModuleInterop": true, - "outDir": "dist", - "declaration": true - }, - "include": ["./test", "./typechain"], - "files": ["./hardhat.config.ts"] -} diff --git a/assets/eip-5027/0001-unlimit-code-size.patch b/assets/eip-5027/0001-unlimit-code-size.patch deleted file mode 100644 index 756bc32..0000000 --- a/assets/eip-5027/0001-unlimit-code-size.patch +++ /dev/null @@ -1,500 +0,0 @@ -From 7b8d4d1b8e00c0515ead0abb3f556e2b5a0617a7 Mon Sep 17 00:00:00 2001 -From: Qi Zhou -Date: Thu, 21 Apr 2022 11:35:27 -0700 -Subject: [PATCH] unlimit code size with cold/warm storage - ---- - core/rawdb/accessors_state.go | 18 +++++++ - core/rawdb/schema.go | 6 +++ - core/state/access_list.go | 32 +++++++++++- - core/state/database.go | 6 +++ - core/state/journal.go | 11 ++++ - core/state/statedb.go | 23 ++++++-- - core/vm/eips.go | 20 +++++++ - core/vm/evm.go | 8 +-- - core/vm/interface.go | 2 + - core/vm/operations_acl.go | 98 +++++++++++++++++++++++++++++++++++ - params/protocol_params.go | 10 ++-- - 11 files changed, 221 insertions(+), 13 deletions(-) - -diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go -index 41e21b6ca..ad7fc150d 100644 ---- a/core/rawdb/accessors_state.go -+++ b/core/rawdb/accessors_state.go -@@ -17,6 +17,8 @@ - package rawdb - - import ( -+ "encoding/binary" -+ - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" -@@ -48,6 +50,16 @@ func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte { - return data - } - -+// ReadCodeSize retrieves the contract code size of the provided code hash. -+// Return 0 if not found -+func ReadCodeSize(db ethdb.KeyValueReader, hash common.Hash) int { -+ data, _ := db.Get(codeSizeKey(hash)) -+ if len(data) != 4 { -+ return 0 -+ } -+ return int(binary.BigEndian.Uint32(data)) -+} -+ - // ReadTrieNode retrieves the trie node of the provided hash. - func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte { - data, _ := db.Get(hash.Bytes()) -@@ -96,6 +108,12 @@ func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) { - if err := db.Put(codeKey(hash), code); err != nil { - log.Crit("Failed to store contract code", "err", err) - } -+ -+ var sizeData [4]byte -+ binary.BigEndian.PutUint32(sizeData[:], uint32(len(code))) -+ if err := db.Put(codeSizeKey(hash), sizeData[:]); err != nil { -+ log.Crit("Failed to store contract code size", "err", err) -+ } - } - - // WriteTrieNode writes the provided trie node database. -diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go -index 08f373488..cbf1dc40f 100644 ---- a/core/rawdb/schema.go -+++ b/core/rawdb/schema.go -@@ -96,6 +96,7 @@ var ( - SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value - CodePrefix = []byte("c") // CodePrefix + code hash -> account code - skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header -+ CodeSizePrefix = []byte("s") // CodePrefixSize - - PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage - configPrefix = []byte("ethereum-config-") // config prefix for the db -@@ -230,6 +231,11 @@ func codeKey(hash common.Hash) []byte { - return append(CodePrefix, hash.Bytes()...) - } - -+// codeSizekey = CodeSizePreifx + hash -+func codeSizeKey(hash common.Hash) []byte { -+ return append(CodeSizePrefix, hash.Bytes()...) -+} -+ - // IsCodeKey reports whether the given byte slice is the key of contract code, - // if so return the raw code hash as well. - func IsCodeKey(key []byte) (bool, []byte) { -diff --git a/core/state/access_list.go b/core/state/access_list.go -index 419469134..22812a936 100644 ---- a/core/state/access_list.go -+++ b/core/state/access_list.go -@@ -21,8 +21,9 @@ import ( - ) - - type accessList struct { -- addresses map[common.Address]int -- slots []map[common.Hash]struct{} -+ addresses map[common.Address]int -+ codeInAddresses map[common.Address]bool -+ slots []map[common.Hash]struct{} - } - - // ContainsAddress returns true if the address is in the access list. -@@ -31,6 +32,12 @@ func (al *accessList) ContainsAddress(address common.Address) bool { - return ok - } - -+// ContainsAddress returns true if the address is in the access list. -+func (al *accessList) ContainsAddressCode(address common.Address) bool { -+ _, ok := al.codeInAddresses[address] -+ return ok -+} -+ - // Contains checks if a slot within an account is present in the access list, returning - // separate flags for the presence of the account and the slot respectively. - func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { -@@ -60,6 +67,9 @@ func (a *accessList) Copy() *accessList { - for k, v := range a.addresses { - cp.addresses[k] = v - } -+ for k, v := range a.codeInAddresses { -+ cp.codeInAddresses[k] = v -+ } - cp.slots = make([]map[common.Hash]struct{}, len(a.slots)) - for i, slotMap := range a.slots { - newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) -@@ -81,6 +91,16 @@ func (al *accessList) AddAddress(address common.Address) bool { - return true - } - -+// AddAddressCode adds the code of an address to the access list, and returns 'true' if the operation -+// caused a change (addr was not previously in the list). -+func (al *accessList) AddAddressCode(address common.Address) bool { -+ if _, present := al.codeInAddresses[address]; present { -+ return false -+ } -+ al.codeInAddresses[address] = true -+ return true -+} -+ - // AddSlot adds the specified (addr, slot) combo to the access list. - // Return values are: - // - address added -@@ -134,3 +154,11 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { - func (al *accessList) DeleteAddress(address common.Address) { - delete(al.addresses, address) - } -+ -+// DeleteAddressCode removes the code of an address from the access list. This operation -+// needs to be performed in the same order as the addition happened. -+// This method is meant to be used by the journal, which maintains ordering of -+// operations. -+func (al *accessList) DeleteAddressCode(address common.Address) { -+ delete(al.codeInAddresses, address) -+} -diff --git a/core/state/database.go b/core/state/database.go -index bbcd2358e..7445e627f 100644 ---- a/core/state/database.go -+++ b/core/state/database.go -@@ -194,6 +194,12 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro - if cached, ok := db.codeSizeCache.Get(codeHash); ok { - return cached.(int), nil - } -+ -+ size := rawdb.ReadCodeSize(db.db.DiskDB(), codeHash) -+ if size != 0 { -+ return size, nil -+ } -+ - code, err := db.ContractCode(addrHash, codeHash) - return len(code), err - } -diff --git a/core/state/journal.go b/core/state/journal.go -index 57a692dc7..8e2250dde 100644 ---- a/core/state/journal.go -+++ b/core/state/journal.go -@@ -134,6 +134,9 @@ type ( - accessListAddAccountChange struct { - address *common.Address - } -+ accessListAddAccountCodeChange struct { -+ address *common.Address -+ } - accessListAddSlotChange struct { - address *common.Address - slot *common.Hash -@@ -260,6 +263,14 @@ func (ch accessListAddAccountChange) dirtied() *common.Address { - return nil - } - -+func (ch accessListAddAccountCodeChange) revert(s *StateDB) { -+ s.accessList.DeleteAddressCode(*ch.address) -+} -+ -+func (ch accessListAddAccountCodeChange) dirtied() *common.Address { -+ return nil -+} -+ - func (ch accessListAddSlotChange) revert(s *StateDB) { - s.accessList.DeleteSlot(*ch.address, *ch.slot) - } -diff --git a/core/state/statedb.go b/core/state/statedb.go -index 1d31cf470..d95dd79aa 100644 ---- a/core/state/statedb.go -+++ b/core/state/statedb.go -@@ -984,11 +984,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { - } - - // PrepareAccessList handles the preparatory steps for executing a state transition with --// regards to both EIP-2929 and EIP-2930: -+// regards to both EIP-2929, EIP-2930, and EIP-5027: - // --// - Add sender to access list (2929) --// - Add destination to access list (2929) --// - Add precompiles to access list (2929) -+// - Add sender to access list (2929, 5027) -+// - Add destination to access list (2929, 5027) -+// - Add precompiles to access list (2929, 5027) - // - Add the contents of the optional tx access list (2930) - // - // This method should only be called if Berlin/2929+2930 is applicable at the current number. -@@ -997,12 +997,15 @@ func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, - s.accessList = newAccessList() - - s.AddAddressToAccessList(sender) -+ s.AddAddressCodeToAccessList(sender) - if dst != nil { - s.AddAddressToAccessList(*dst) -+ s.AddAddressCodeToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range precompiles { - s.AddAddressToAccessList(addr) -+ s.AddAddressCodeToAccessList(addr) - } - for _, el := range list { - s.AddAddressToAccessList(el.Address) -@@ -1019,6 +1022,13 @@ func (s *StateDB) AddAddressToAccessList(addr common.Address) { - } - } - -+// AddAddressCodeToAccessList adds the given address to the access list -+func (s *StateDB) AddAddressCodeToAccessList(addr common.Address) { -+ if s.accessList.AddAddressCode(addr) { -+ s.journal.append(accessListAddAccountCodeChange{&addr}) -+ } -+} -+ - // AddSlotToAccessList adds the given (address, slot)-tuple to the access list - func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { - addrMod, slotMod := s.accessList.AddSlot(addr, slot) -@@ -1042,6 +1052,11 @@ func (s *StateDB) AddressInAccessList(addr common.Address) bool { - return s.accessList.ContainsAddress(addr) - } - -+// AddressCodeInAccessList returns true if the given address's code is in the access list. -+func (s *StateDB) AddressCodeInAccessList(addr common.Address) bool { -+ return s.accessList.ContainsAddressCode(addr) -+} -+ - // SlotInAccessList returns true if the given (address, slot)-tuple is in the access list. - func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { - return s.accessList.Contains(addr, slot) -diff --git a/core/vm/eips.go b/core/vm/eips.go -index 4070a2db5..e9a8ee78c 100644 ---- a/core/vm/eips.go -+++ b/core/vm/eips.go -@@ -31,6 +31,7 @@ var activators = map[int]func(*JumpTable){ - 2200: enable2200, - 1884: enable1884, - 1344: enable1344, -+ 5027: enable5027, - } - - // EnableEIP enables the given EIP on the config. -@@ -147,6 +148,25 @@ func enable2929(jt *JumpTable) { - jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 - } - -+// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" -+// https://eips.ethereum.org/EIPS/eip-2929 -+func enable5027(jt *JumpTable) { -+ jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 -+ jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP5027 -+ -+ jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 -+ jt[CALL].dynamicGas = gasCallEIP5027 -+ -+ jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 -+ jt[CALLCODE].dynamicGas = gasCallCodeEIP5027 -+ -+ jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 -+ jt[STATICCALL].dynamicGas = gasStaticCallEIP5027 -+ -+ jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 -+ jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP5027 -+} -+ - // enable3529 enabled "EIP-3529: Reduction in refunds": - // - Removes refunds for selfdestructs - // - Reduces refunds for SSTORE -diff --git a/core/vm/evm.go b/core/vm/evm.go -index dd55618bf..99e57c28e 100644 ---- a/core/vm/evm.go -+++ b/core/vm/evm.go -@@ -421,6 +421,8 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, - // the access-list change should not be rolled back - if evm.chainRules.IsBerlin { - evm.StateDB.AddAddressToAccessList(address) -+ // TODO: check shanghai -+ evm.StateDB.AddAddressCodeToAccessList(address) - } - // Ensure there's no existing contract already at the designated address - contractHash := evm.StateDB.GetCodeHash(address) -@@ -453,9 +455,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, - ret, err := evm.interpreter.Run(contract, nil, false) - - // Check whether the max code size has been exceeded, assign err if the case. -- if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { -- err = ErrMaxCodeSizeExceeded -- } -+ // if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { -+ // err = ErrMaxCodeSizeExceeded -+ // } - - // Reject code starting with 0xEF if EIP-3541 is enabled. - if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { -diff --git a/core/vm/interface.go b/core/vm/interface.go -index ad9b05d66..12660dd08 100644 ---- a/core/vm/interface.go -+++ b/core/vm/interface.go -@@ -59,6 +59,7 @@ type StateDB interface { - - PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) - AddressInAccessList(addr common.Address) bool -+ AddressCodeInAccessList(addr common.Address) bool - SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) - // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform - // even if the feature/fork is not active yet -@@ -66,6 +67,7 @@ type StateDB interface { - // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform - // even if the feature/fork is not active yet - AddSlotToAccessList(addr common.Address, slot common.Hash) -+ AddAddressCodeToAccessList(addr common.Address) - - RevertToSnapshot(int) - Snapshot() int -diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go -index 551e1f5f1..cb76a4390 100644 ---- a/core/vm/operations_acl.go -+++ b/core/vm/operations_acl.go -@@ -138,6 +138,41 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo - return gas, nil - } - -+// gasExtCodeCopyEIP5027 implements extcodecopy according to EIP-5027 -+// EIP spec: -+// > If the target is not in accessed_addresses, -+// > charge COLD_ACCOUNT_ACCESS_COST * N_CODE_UNIT gas, and add the address to accessed_addresses and accessed_code_in_addresses. -+// > Else if the target is not in accessed_code_in_addresses, -+// > charge COLD_ACCOUNT_ACCESS_COST * (N_CODE_UNIT - 1) gas, and add the address to accessed_code_in_addresses. -+// > Otherwise, charge WARM_STORAGE_READ_COST gas. -+func gasExtCodeCopyEIP5027(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { -+ // memory expansion first (dynamic part of pre-5027 implementation) -+ gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) -+ if err != nil { -+ return 0, err -+ } -+ addr := common.Address(stack.peek().Bytes20()) -+ // Check slot presence in the access list -+ if !evm.StateDB.AddressInAccessList(addr) { -+ evm.StateDB.AddAddressToAccessList(addr) -+ var overflow bool -+ // We charge (cold-warm), since 'warm' is already charged as constantGas -+ if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { -+ return 0, ErrGasUintOverflow -+ } -+ } -+ if !evm.StateDB.AddressCodeInAccessList(addr) { -+ evm.StateDB.AddAddressCodeToAccessList(addr) -+ var overflow bool -+ -+ // We charge cold for extra code -+ if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929*getExtraCodeUnit(evm, addr)); overflow { -+ return 0, ErrGasUintOverflow -+ } -+ } -+ return gas, nil -+} -+ - // gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list. - // If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it - // is also using 'warm' as constant factor. -@@ -191,6 +226,64 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { - } - } - -+func getExtraCodeUnit(evm *EVM, addr common.Address) uint64 { -+ codeSize := evm.StateDB.GetCodeSize(addr) -+ extraCodeUnit := uint64(0) -+ if codeSize > params.CodeSizeUnit { -+ extraCodeUnit = (uint64(codeSize - 1)) / params.CodeSizeUnit -+ } -+ return extraCodeUnit -+} -+ -+func makeCallVariantGasCallEIP5027(oldCalculator gasFunc) gasFunc { -+ return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { -+ addr := common.Address(stack.Back(1).Bytes20()) -+ // Check slot presence in the access list -+ warmAccess := evm.StateDB.AddressInAccessList(addr) -+ warmCodeAccess := evm.StateDB.AddressCodeInAccessList(addr) -+ // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so -+ // the cost to charge for cold access, if any, is n * Cold - Warm -+ coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 -+ -+ if !warmAccess { -+ evm.StateDB.AddAddressToAccessList(addr) -+ evm.StateDB.AddAddressCodeToAccessList(addr) -+ -+ coldCost += getExtraCodeUnit(evm, addr) * params.ColdAccountCodeAccessCostEIP5027 -+ -+ // Charge the remaining difference here already, to correctly calculate available -+ // gas for call -+ if !contract.UseGas(coldCost) { -+ return 0, ErrOutOfGas -+ } -+ } else if !warmCodeAccess { -+ evm.StateDB.AddAddressCodeToAccessList(addr) -+ -+ coldCost = getExtraCodeUnit(evm, addr) * params.ColdAccountCodeAccessCostEIP5027 -+ // Charge the remaining difference here already, to correctly calculate available -+ // gas for call -+ if !contract.UseGas(coldCost) { -+ return 0, ErrOutOfGas -+ } -+ } -+ // Now call the old calculator, which takes into account -+ // - create new account -+ // - transfer value -+ // - memory expansion -+ // - 63/64ths rule -+ gas, err := oldCalculator(evm, contract, stack, mem, memorySize) -+ if (warmAccess && warmCodeAccess) || err != nil { -+ return gas, err -+ } -+ // In case of a cold access, we temporarily add the cold charge back, and also -+ // add it to the returned gas. By adding it to the return, it will be charged -+ // outside of this function, as part of the dynamic gas, and that will make it -+ // also become correctly reported to tracers. -+ contract.Gas += coldCost -+ return gas + coldCost, nil -+ } -+} -+ - var ( - gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall) - gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall) -@@ -200,6 +293,11 @@ var ( - // gasSelfdestructEIP3529 implements the changes in EIP-2539 (no refunds) - gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) - -+ gasCallEIP5027 = makeCallVariantGasCallEIP5027(gasCall) -+ gasDelegateCallEIP5027 = makeCallVariantGasCallEIP5027(gasDelegateCall) -+ gasStaticCallEIP5027 = makeCallVariantGasCallEIP5027(gasStaticCall) -+ gasCallCodeEIP5027 = makeCallVariantGasCallEIP5027(gasCallCode) -+ - // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 - // - // When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. -diff --git a/params/protocol_params.go b/params/protocol_params.go -index 5f154597a..c3d5c66ce 100644 ---- a/params/protocol_params.go -+++ b/params/protocol_params.go -@@ -58,9 +58,11 @@ const ( - SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else - SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot - -- ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST -- ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST -- WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST -+ ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST -+ ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST -+ WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST -+ ColdAccountCodeAccessCostEIP5027 = uint64(2600) // COLD_ACCOUNT_CODE_ACCESS_COST_PER_UNIT -+ WarmAccountCodeAccessCostEIP5027 = uint64(2600) // WARM_ACCOUNT_CODE_ACCESS_COST_PER_UNIT - - // In EIP-2200: SstoreResetGas was 5000. - // In EIP-2929: SstoreResetGas was changed to '5000 - COLD_SLOAD_COST'. -@@ -123,7 +125,7 @@ const ( - ElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. - InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. - -- MaxCodeSize = 24576 // Maximum bytecode to permit for a contract -+ CodeSizeUnit = 24576 // Code size unit for gas metering. - - // Precompiled contract gas prices - --- -2.30.1 (Apple Git-130) - diff --git a/assets/eip-5050/ActionsSet.sol b/assets/eip-5050/ActionsSet.sol deleted file mode 100644 index 36be830..0000000 --- a/assets/eip-5050/ActionsSet.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Based on OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol) - -pragma solidity ^0.8.0; - -library ActionsSet { - struct Set { - // Storage of action names - string[] _names; - // Storage of action selectors - bytes4[] _selectors; - // Position of the value in the `values` array, plus 1 because index 0 - // means a value is not in the set. - mapping(bytes4 => uint256) _indexes; - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(Set storage set, string memory name) internal returns (bool) { - bytes4 selector = bytes4(keccak256(bytes(name))); - if (!contains(set, selector)) { - set._selectors.push(selector); - set._names.push(name); - // The value is stored at length-1, but we add 1 to all indexes - // and use 0 as a sentinel value - set._indexes[selector] = set._selectors.length; - return true; - } else { - return false; - } - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(Set storage set, bytes4 value) internal returns (bool) { - // We read and store the value's index to prevent multiple reads from the same storage slot - uint256 valueIndex = set._indexes[value]; - - if (valueIndex != 0) { - // Equivalent to contains(set, value) - // To delete an element from the _selectors array in O(1), we swap the element to delete with the last one in - // the array, and then remove the last element (sometimes called as 'swap and pop'). - // This modifies the order of the array, as noted in {at}. - - uint256 toDeleteIndex = valueIndex - 1; - uint256 lastIndex = set._selectors.length - 1; - - if (lastIndex != toDeleteIndex) { - bytes4 lastValue = set._selectors[lastIndex]; - - // Move the last value to the index where the value to delete is - set._selectors[toDeleteIndex] = lastValue; - // Update the index for the moved value - set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex - } - - // Delete the slot where the moved value was stored - set._selectors.pop(); - - // Delete the index for the deleted slot - delete set._indexes[value]; - - return true; - } else { - return false; - } - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(Set storage set, bytes4 value) - internal - view - returns (bool) - { - return set._indexes[value] != 0; - } - - /** - * @dev Returns the number of values on the set. O(1). - */ - function length(Set storage set) internal view returns (uint256) { - return set._selectors.length; - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(Set storage set, uint256 index) internal view returns (bytes4) { - return set._selectors[index]; - } - - /** - * @dev Return the entire set of action names - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function names(Set storage set) internal view returns (string[] memory) { - return set._names; - } - - /** - * @dev Return the entire set of action selectors - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function selectors(Set storage set) internal view returns (bytes4[] memory) { - return set._selectors; - } - - -} diff --git a/assets/eip-5050/ERC5050.sol b/assets/eip-5050/ERC5050.sol deleted file mode 100644 index e2608d9..0000000 --- a/assets/eip-5050/ERC5050.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5050Sender.sol"; -import "./ERC5050Receiver.sol"; - -contract ERC5050 is ERC5050Sender, ERC5050Receiver { - function _registerAction(bytes4 action) internal { - _registerReceivable(action); - _registerSendable(action); - } -} diff --git a/assets/eip-5050/ERC5050Receiver.sol b/assets/eip-5050/ERC5050Receiver.sol deleted file mode 100644 index 254d01d..0000000 --- a/assets/eip-5050/ERC5050Receiver.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {IERC5050Sender, IERC5050Receiver, Action} from "./IERC5050.sol"; -import {ActionsSet} from "./ActionsSet.sol"; - -contract ERC5050Receiver is IERC5050Receiver { - using ActionsSet for ActionsSet.Set; - - ActionsSet.Set _receivableActions; - - modifier onlyReceivableAction(Action calldata action, uint256 nonce) { - require( - action.to._address == address(this), - "ERC5050: invalid receiver" - ); - require( - _receivableActions.contains(action.selector), - "ERC5050: invalid action" - ); - require( - action.from._address == address(0) || - action.from._address == msg.sender, - "ERC5050: invalid sender" - ); - require( - (action.from._address != address(0) && action.user == tx.origin) || - action.user == msg.sender, - "ERC5050: invalid sender" - ); - _; - } - - function receivableActions() external view returns (string[] memory) { - return _receivableActions.names(); - } - - function onActionReceived(Action calldata action, uint256 nonce) - external - payable - virtual - override - onlyReceivableAction(action, nonce) - { - _onActionReceived(action, nonce); - } - - function _onActionReceived(Action calldata action, uint256 nonce) - internal - virtual - { - if (action.state != address(0)) { - require(action.state.isContract(), "ERC5050: invalid state"); - try - IERC5050Receiver(action.state).onActionReceived{ - value: msg.value - }(action, nonce) - {} catch (bytes memory reason) { - if (reason.length == 0) { - revert("ERC5050: call to non ERC5050Receiver"); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - emit ActionReceived( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data - ); - } - - function _registerReceivable(string memory action) internal { - _receivableActions.add(action); - } -} diff --git a/assets/eip-5050/ERC5050Sender.sol b/assets/eip-5050/ERC5050Sender.sol deleted file mode 100644 index 3eb6730..0000000 --- a/assets/eip-5050/ERC5050Sender.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {IERC5050Sender, IERC5050Receiver, Action} from "./IERC5050.sol"; -import {ActionsSet} from "./ActionsSet.sol"; - -contract ERC5050Sender is IERC5050Sender { - using ActionsSet for ActionsSet.Set; - - ActionsSet.Set _sendableActions; - - uint256 private _nonce; - bytes32 private _hash; - - mapping(address => mapping(bytes4 => address)) actionApprovals; - mapping(address => mapping(address => bool)) operatorApprovals; - - function sendAction(Action memory action) - external - payable - virtual - override - { - _sendAction(action); - } - - function isValid(bytes32 actionHash, uint256 nonce) - external - view - returns (bool) - { - return actionHash == _hash && nonce == _nonce; - } - - function sendableActions() external view returns (string[] memory) { - return _sendableActions.names(); - } - - modifier onlySendableAction(Action memory action) { - require( - _sendableActions.contains(action.selector), - "ERC5050: invalid action" - ); - require( - _isApprovedOrSelf(action.user, action.selector), - "ERC5050: unapproved sender" - ); - _; - } - - function approveForAction( - address _account, - bytes4 _action, - address _approved - ) public virtual override returns (bool) { - require(_approved != _account, "ERC5050: approve to caller"); - - require( - msg.sender == _account || - isApprovedForAllActions(_account, msg.sender), - "ERC5050: approve caller is not account nor approved for all" - ); - - actionApprovals[_account][_action] = _approved; - emit ApprovalForAction(_account, _action, _approved); - - return true; - } - - function setApprovalForAllActions(address _operator, bool _approved) - public - virtual - override - { - require(msg.sender != _operator, "ERC5050: approve to caller"); - - operatorApprovals[msg.sender][_operator] = _approved; - - emit ApprovalForAllActions(msg.sender, _operator, _approved); - } - - function getApprovedForAction(address _account, bytes4 _action) - public - view - returns (address) - { - return actionApprovals[_account][_action]; - } - - function isApprovedForAllActions(address _account, address _operator) - public - view - returns (bool) - { - return operatorApprovals[_account][_operator]; - } - - function _sendAction(Action memory action) internal { - action.from._address = address(this); - bool toIsContract = action.to._address.isContract(); - bool stateIsContract = action.state.isContract(); - address next; - if (toIsContract) { - next = action.to._address; - } else if (stateIsContract) { - next = action.state; - } - uint256 nonce; - if (toIsContract && stateIsContract) { - _validate(action); - nonce = _nonce; - } - if (next.isContract()) { - try - IERC5050Receiver(next).onActionReceived{value: msg.value}( - action, - nonce - ) - {} catch Error(string memory err) { - revert(err); - } catch (bytes memory returnData) { - if (returnData.length > 0) { - revert(string(returnData)); - } - } - } - emit SendAction( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data - ); - } - - function _validate(Action memory action) internal { - ++_nonce; - _hash = bytes32( - keccak256( - abi.encodePacked( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data, - _nonce - ) - ) - ); - } - - function _isApprovedOrSelf(address account, bytes4 action) - internal - view - returns (bool) - { - return (msg.sender == account || - isApprovedForAllActions(account, msg.sender) || - getApprovedForAction(account, action) == msg.sender); - } - - function _registerSendable(string memory action) internal { - _sendableActions.add(action); - } -} diff --git a/assets/eip-5050/ERC5050State.sol b/assets/eip-5050/ERC5050State.sol deleted file mode 100644 index 55567a3..0000000 --- a/assets/eip-5050/ERC5050State.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {IERC5050Sender, IERC5050Receiver, Action} from "./IERC5050.sol"; -import {ActionsSet} from "./ActionsSet.sol"; - -contract ERC5050State is IERC5050Receiver { - using ActionsSet for ActionsSet.Set; - - ActionsSet.Set private _receivableActions; - - function onActionReceived(Action calldata action, uint256 nonce) - external - payable - virtual - override - onlyReceivableAction(action, nonce) - { - _onActionReceived(action, nonce); - } - - function receivableActions() external view returns (string[] memory) { - return _receivableActions.names(); - } - - modifier onlyReceivableAction(Action calldata action, uint256 nonce) { - require( - _receivableActions.contains(action.selector), - "ERC5050: invalid action" - ); - require(action.state == address(this), "ERC5050: invalid state"); - require( - action.user == address(0) || action.user == tx.origin, - "ERC5050: invalid user" - ); - - address expectedSender = action.to._address; - if (expectedSender == address(0)) { - if (action.from._address != address(0)) { - expectedSender = action.from._address; - } else { - expectedSender = action.user; - } - } - require(msg.sender == expectedSender, "ERC5050: invalid sender"); - - // State contracts must validate the action with the `from` contract in - // the case of a 3-contract chain (`from`, `to` and `state`) all set to - // valid contract addresses. - if ( - action.to._address.isContract() && action.from._address.isContract() - ) { - uint256 actionHash = uint256( - keccak256( - abi.encodePacked( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data, - nonce - ) - ) - ); - try - IERC5050Sender(action.from._address).isValid(actionHash, nonce) - returns (bool ok) { - require(ok, "ERC5050: action not validated"); - } catch (bytes memory reason) { - if (reason.length == 0) { - revert("ERC5050: call to non ERC5050Sender"); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - _; - } - - function _onActionReceived(Action calldata action, uint256 nonce) - internal - virtual - { - emit ActionReceived( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data - ); - } - - function _registerReceivable(string memory action) internal { - _receivableActions.add(action); - } -} diff --git a/assets/eip-5050/ExampleStateContract.sol b/assets/eip-5050/ExampleStateContract.sol deleted file mode 100644 index a609bc9..0000000 --- a/assets/eip-5050/ExampleStateContract.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {ERC5050State, Action} from "./ERC5050State.sol"; -import {ERC5050, Action} from "./ERC5050.sol"; - -struct TokenInfo { - uint256 health; - uint256 healthRemaining; - uint256 power; - uint256 blockedAt; - uint256 blockPower; - uint256 lockedUntilBlock; - uint256 wins; - bool hasRegistered; -} - -interface IFightGame { - function getStats(address _contract, uint256 _tokenId) external view returns (TokenInfo); -} - -contract FightGame is IFightGame, ERC5050State { - - bytes4 constant LIGHT_ATTACK_SELECTOR = bytes4(keccak256("fg.light-attack")); - bytes4 constant HEAVY_ATTACK_SELECTOR = bytes4(keccak256("fg.heavy-attack")); - bytes4 constant BLOCK_SELECTOR = bytes4(keccak256("fg.block")); - - uint256 constant BLOCK_DECAY = 100; - uint256 constant LIGHT_ATTACK_DECAY = 200; - uint256 constant HEAVY_ATTACK_DECAY = 500; - - mapping(address => mapping(uint256 => TokenInfo)) state; - - constructor() { - _registerReceivable("fg.light-attack"); - _registerReceivable("fg.heavy-attack"); - _registerReceivable("fg.block"); - } - - function register(address _contract, uint256 _tokenId) external { - require(msg.sender == ownerOf(_contract, _tokenId), "sender not token owner"); - require(!state[_contract][_tokenId].hasRegistered, "token already registered"); - state[_contract][_tokenId] = TokenInfo(100, 100, 5, 0, 0, 0, true); - } - - function getStats(address _contract, uint256 _tokenId) external view returns (TokenInfo){ - return state[_contract][_tokenId]; - } - - function onActionReceived(Action calldata action, uint256 _nonce) - external - payable - override - onlyReceivableAction(action, _nonce) - { - TokenInfo storage from = state[action.from._address][action.from._tokenId]; - require(from.healthRemaining > 0, "health 0"); - require(block.number > from.lockedUntilBlock, "token locked"); - if (action.selector == BLOCK_SELECTOR) { - from.blockPower = from.power * 3; - from.blockedAt = block.number; - from.lockedUntilBlock = block.number + BLOCK_DECAY; - return; - } - - TokenInfo storage to = state[action.to._address][action.to._tokenId]; - require(to.healthRemaining > 0, "target health 0"); - - uint256 damage; - if (action.selector == LIGHT_ATTACK_SELECTOR ) { - damage = from.power; - from.lockedUntilBlock = block.number + LIGHT_ATTACK_DECAY; - } - - if (action.selector == HEAVY_ATTACK_SELECTOR) { - damage = from.power * 3; - from.lockedUntilBlock = block.number + HEAVY_ATTACK_DECAY; - } - if(to.blockedAt + BLOCK_DECAY > block.number) { - if(to.blockPower >= damage){ - to.blockPower -= damage; - return; - } - damage -= to.blockPower; - } - if(to.healthRemaining > damage){ - to.healthRemaining -= damage; - return; - } - - // Winner gains loser's power and some health - from.power += to.power; - from.healthRemaining += to.power; - from.wins++; - to.healthRemaining = 0; - } -} - -contract Fighter is ERC5050, ERC721 { - - IFightGame stateContract; - - constructor(address _stateContract) { - _registerAction("fg.light-attack"); - _registerAction("fg.heavy-attack"); - _registerSendable("fg.block"); - stateContract = IFightGame(_stateContract); - } - - // Update NFT render / metadata based on game stats - function tokenURICharacterEmoji(uint256 tokenId) - public - view - override - returns (string memory) - { - TokenInfo memory stats = stateContract.getStats(address(this), tokenId); - if(stats.healthRemaining == 0){ - return unicode"😵"; - } - if(stats.power > 100){ - return unicode"🦾"; - } - if(stats.power > 50){ - return unicode"💪"; - } - if(stats.power > 20){ - return unicode"🤩"; - } - if(stats.power > 5){ - return unicode"😃"; - } - return unicode"😀"; - } -} \ No newline at end of file diff --git a/assets/eip-5050/ExampleToken2Token.sol b/assets/eip-5050/ExampleToken2Token.sol deleted file mode 100644 index 53e0b02..0000000 --- a/assets/eip-5050/ExampleToken2Token.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {ERC5050, Action} from "./ERC5050.sol"; - -contract Spells is ERC5050, ERC721 { - - bytes4 constant CAST_SELECTOR = bytes4(keccak256("cast")); - bytes4 constant ATTUNE_SELECTOR = bytes4(keccak256("attune")); - - mapping(uint256 => uint256) spellDust; - mapping(uint256 => string) attunement; - - constructor() ERC721("Spells", unicode"🔮") { - _registerSendable("cast"); - _registerReceivable("attune"); - } - - function sendAction(Action memory action) - external - payable - override - onlySendableAction(action) - { - require( - msg.sender == ownerOf(action.from._tokenId), - "Spells: invalid sender" - ); - _sendAction(action); - } - - function onActionReceived(Action calldata action, uint256 _nonce) - external - payable - override - onlyReceivableAction(action, _nonce) - { - if (action.selector == ATTUNE_SELECTOR) { - string memory unicodeChar; - bytes memory _data = action.data; - assembly { - // Read unicode character from first 6 bytes (\u5050) - unicodeChar := shr(208, _data) - } - attunement[action.to._tokenId] = unicodeChar; - } - // Pass action to state receiver if specified - _onActionReceived(action, _nonce); - } - - string[12] private dust = [ - unicode"․", - unicode"∴", - unicode"`" - ]; - - string[5] private spells = [ - "Conjuring", - "Divining", - "Transforming", - "Hexing", - "Banishing" - ]; - - function tokenURI(uint256 tokenId) - public - view - override - returns (string memory) - { - string - memory out = ''; - - out = string.concat( - out, - string.concat(spells[_spellType(tokenId)], " Spell"), - '', - attunement[tokenId] - ); - out = string.concat(out, ""); - string memory json = Base64.encode( - bytes( - string( - abi.encodePacked( - '{"name": "Spell #', - Strings.toString(tokenId), - '", "description": "Cast spells, attune spells.", "image": "data:image/svg+xml;base64,', - Base64.encode(bytes(out)), - '"}' - ) - ) - ) - ); - return string(abi.encodePacked("data:application/json;base64,", json)); - } - - function _spellType(uint256 tokenId) internal pure returns (uint256) { - uint256 rand = _random(Strings.toString(tokenId)); - return rand % 6; - } - - function _random(string memory input) internal pure returns (uint256) { - return uint256(keccak256(abi.encodePacked(input))); - } -} \ No newline at end of file diff --git a/assets/eip-5050/IERC5050.sol b/assets/eip-5050/IERC5050.sol deleted file mode 100644 index 95cf778..0000000 --- a/assets/eip-5050/IERC5050.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/// @title ERC-xxxx Token Interaction Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-xxx -interface IERC5050Sender { - /// @notice Send an action to the target address - /// @dev The action's `fromContract` is automatically set to `address(this)`, - /// and the `from` parameter is set to `msg.sender`. - /// @param action The action to send - function sendAction(Action memory action) external payable; - - /// @notice Check if an action is valid based on its hash and nonce - /// @dev When an action passes through all three possible contracts - /// (`fromContract`, `to`, and `state`) the `state` contract validates the - /// action with the initiating `fromContract` using a nonced action hash. - /// This hash is calculated and saved to storage on the `fromContract` before - /// action handling is initiated. The `state` contract calculates the hash - /// and verifies it and nonce with the `fromContract`. - /// @param _hash The hash to validate - /// @param _nonce The nonce to validate - function isValid(bytes32 _hash, uint256 _nonce) external returns (bool); - - /// @notice Retrieve list of actions that can be sent. - /// @dev Intended for use by off-chain applications to query compatible contracts. - function sendableActions() external view returns (string[] memory); - - /// @notice Change or reaffirm the approved address for an action - /// @dev The zero address indicates there is no approved address. - /// Throws unless `msg.sender` is the `_account`, or an authorized - /// operator of the `_account`. - /// @param _account The account of the account-action pair to approve - /// @param _action The action of the account-action pair to approve - /// @param _approved The new approved account-action controller - function approveForAction( - address _account, - bytes4 _action, - address _approved - ) external returns (bool); - - /// @notice Enable or disable approval for a third party ("operator") to conduct - /// all actions on behalf of `msg.sender` - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// multiple operators per owner. - /// @param _operator Address to add to the set of authorized operators - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAllActions(address _operator, bool _approved) - external; - - /// @notice Get the approved address for an account-action pair - /// @dev Throws if `_tokenId` is not a valid NFT. - /// @param _account The account of the account-action to find the approved address for - /// @param _action The action of the account-action to find the approved address for - /// @return The approved address for this account-action, or the zero address if - /// there is none - function getApprovedForAction(address _account, bytes4 _action) - external - view - returns (address); - - /// @notice Query if an address is an authorized operator for another address - /// @param _account The address on whose behalf actions are performed - /// @param _operator The address that acts on behalf of the account - /// @return True if `_operator` is an approved operator for `_account`, false otherwise - function isApprovedForAllActions(address _account, address _operator) - external - view - returns (bool); - - /// @dev This emits when an action is sent (`sendAction()`) - event SendAction( - bytes4 indexed name, - address _from, - address indexed _fromContract, - uint256 _tokenId, - address indexed _to, - uint256 _toTokenId, - address _state, - bytes _data - ); - - /// @dev This emits when the approved address for an account-action pair - /// is changed or reaffirmed. The zero address indicates there is no - /// approved address. - event ApprovalForAction( - address indexed _account, - bytes4 indexed _action, - address indexed _approved - ); - - /// @dev This emits when an operator is enabled or disabled for an account. - /// The operator can conduct all actions on behalf of the account. - event ApprovalForAllActions( - address indexed _account, - address indexed _operator, - bool _approved - ); -} - -interface IERC5050Receiver { - /// @notice Handle an action - /// @dev Both the `to` contract and `state` contract are called via - /// `onActionReceived()`. - /// @param action The action to handle - function onActionReceived(Action calldata action, uint256 _nonce) - external - payable; - - /// @notice Retrieve list of actions that can be received. - /// @dev Intended for use by off-chain applications to query compatible contracts. - function receivableActions() external view returns (string[] memory); - - /// @dev This emits when a valid action is received. - event ActionReceived( - bytes4 indexed name, - address _from, - address indexed _fromContract, - uint256 _tokenId, - address indexed _to, - uint256 _toTokenId, - address _state, - bytes _data - ); -} - -/// @param _address The address of the interactive object -/// @param tokenId The token that is interacting (optional) -struct Object { - address _address; - uint256 _tokenId; -} - -/// @param name The name of the action -/// @param user The address of the sender -/// @param from The initiating object -/// @param to The receiving object -/// @param state The state contract -/// @param data Additional data with no specified format -struct Action { - bytes4 selector; - address user; - Object from; - Object to; - address state; - bytes data; -} diff --git a/assets/eip-5058/ERC5058.sol b/assets/eip-5058/ERC5058.sol deleted file mode 100644 index 90fc3ef..0000000 --- a/assets/eip-5058/ERC5058.sol +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC5058.sol"; - -/** - * @dev Implementation ERC721 Lockable Token - */ -abstract contract ERC5058 is ERC721, IERC5058 { - // Mapping from token ID to unlock time - mapping(uint256 => uint256) public lockedTokens; - - // Mapping from token ID to lock approved address - mapping(uint256 => address) private _lockApprovals; - - // Mapping from owner to lock operator approvals - mapping(address => mapping(address => bool)) private _lockOperatorApprovals; - - /** - * @dev See {IERC5058-lockApprove}. - */ - function lockApprove(address to, uint256 tokenId) public virtual override { - require(!isLocked(tokenId), "ERC5058: token is locked"); - address owner = ERC721.ownerOf(tokenId); - require(to != owner, "ERC5058: lock approval to current owner"); - - require( - _msgSender() == owner || isLockApprovedForAll(owner, _msgSender()), - "ERC5058: lock approve caller is not owner nor approved for all" - ); - - _lockApprove(owner, to, tokenId); - } - - /** - * @dev See {IERC5058-getLockApproved}. - */ - function getLockApproved(uint256 tokenId) public view virtual override returns (address) { - require(_exists(tokenId), "ERC5058: lock approved query for nonexistent token"); - - return _lockApprovals[tokenId]; - } - - /** - * @dev See {IERC5058-lockerOf}. - */ - function lockerOf(uint256 tokenId) public view virtual override returns (address) { - require(_exists(tokenId), "ERC5058: locker query for nonexistent token"); - require(isLocked(tokenId), "ERC5058: locker query for non-locked token"); - - return _lockApprovals[tokenId]; - } - - /** - * @dev See {IERC5058-setLockApprovalForAll}. - */ - function setLockApprovalForAll(address operator, bool approved) public virtual override { - _setLockApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @dev See {IERC5058-isLockApprovedForAll}. - */ - function isLockApprovedForAll(address owner, address operator) public view virtual override returns (bool) { - return _lockOperatorApprovals[owner][operator]; - } - - /** - * @dev See {IERC5058-isLocked}. - */ - function isLocked(uint256 tokenId) public view virtual override returns (bool) { - return lockedTokens[tokenId] > block.number; - } - - /** - * @dev See {IERC5058-lockExpiredTime}. - */ - function lockExpiredTime(uint256 tokenId) public view virtual override returns (uint256) { - return lockedTokens[tokenId]; - } - - /** - * @dev See {IERC5058-lock}. - */ - function lock(uint256 tokenId, uint256 expired) public virtual override { - //solhint-disable-next-line max-line-length - require(_isLockApprovedOrOwner(_msgSender(), tokenId), "ERC5058: lock caller is not owner nor approved"); - require(expired > block.number, "ERC5058: expired time must be greater than current block number"); - require(!isLocked(tokenId), "ERC5058: token is locked"); - - _lock(_msgSender(), tokenId, expired); - } - - /** - * @dev See {IERC5058-unlock}. - */ - function unlock(uint256 tokenId) public virtual override { - require(lockerOf(tokenId) == _msgSender(), "ERC5058: unlock caller is not lock operator"); - - address from = ERC721.ownerOf(tokenId); - - _beforeTokenLock(_msgSender(), from, tokenId, 0); - - delete lockedTokens[tokenId]; - - emit Unlocked(_msgSender(), from, tokenId); - - _afterTokenLock(_msgSender(), from, tokenId, 0); - } - - /** - * @dev Locks `tokenId` from `from` until `expired`. - * - * Requirements: - * - * - `tokenId` token must be owned by `from`. - * - * Emits a {Locked} event. - */ - function _lock( - address operator, - uint256 tokenId, - uint256 expired - ) internal virtual { - address owner = ERC721.ownerOf(tokenId); - - _beforeTokenLock(operator, owner, tokenId, expired); - - lockedTokens[tokenId] = expired; - _lockApprovals[tokenId] = operator; - - emit Locked(operator, owner, tokenId, expired); - - _afterTokenLock(operator, owner, tokenId, expired); - } - - /** - * @dev Safely mints `tokenId` and transfers it to `to`, but the `tokenId` is locked and cannot be transferred. - * - * Requirements: - * - * - `tokenId` must not exist. - * - * Emits {Locked} and {Transfer} event. - */ - function _safeLockMint( - address to, - uint256 tokenId, - uint256 expired, - bytes memory _data - ) internal virtual { - require(expired > block.number, "ERC5058: lock mint for invalid lock block number"); - - _safeMint(to, tokenId, _data); - - _lock(_msgSender(), tokenId, expired); - } - - /** - * @dev See {ERC721-_burn}. This override additionally clears the lock approvals for the token. - */ - function _burn(uint256 tokenId) internal virtual override { - address owner = ERC721.ownerOf(tokenId); - super._burn(tokenId); - - _beforeTokenLock(_msgSender(), owner, tokenId, 0); - - // clear lock approvals - delete lockedTokens[tokenId]; - delete _lockApprovals[tokenId]; - - _afterTokenLock(_msgSender(), owner, tokenId, 0); - } - - /** - * @dev Approve `to` to lock operate on `tokenId` - * - * Emits a {LockApproval} event. - */ - function _lockApprove( - address owner, - address to, - uint256 tokenId - ) internal virtual { - _lockApprovals[tokenId] = to; - emit LockApproval(owner, to, tokenId); - } - - /** - * @dev Approve `operator` to lock operate on all of `owner` tokens - * - * Emits a {LockApprovalForAll} event. - */ - function _setLockApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "ERC5058: lock approve to caller"); - _lockOperatorApprovals[owner][operator] = approved; - emit LockApprovalForAll(owner, operator, approved); - } - - /** - * @dev Returns whether `spender` is allowed to lock `tokenId`. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function _isLockApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { - require(_exists(tokenId), "ERC5058: lock operator query for nonexistent token"); - address owner = ERC721.ownerOf(tokenId); - return (spender == owner || isLockApprovedForAll(owner, spender) || getLockApproved(tokenId) == spender); - } - - /** - * @dev See {ERC721-_beforeTokenTransfer}. - * - * Requirements: - * - * - the `tokenId` must not be locked. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._beforeTokenTransfer(from, to, tokenId); - - require(!isLocked(tokenId), "ERC5058: token transfer while locked"); - } - - /** - * @dev Hook that is called before any token lock/unlock. - * - * Calling conditions: - * - * - `owner` is non-zero. - * - When `expired` is zero, `tokenId` will be unlock for `from`. - * - When `expired` is non-zero, ``from``'s `tokenId` will be locked. - * - */ - function _beforeTokenLock( - address operator, - address owner, - uint256 tokenId, - uint256 expired - ) internal virtual {} - - /** - * @dev Hook that is called after any lock/unlock of tokens. - * - * Calling conditions: - * - * - `owner` is non-zero. - * - When `expired` is zero, `tokenId` will be unlock for `from`. - * - When `expired` is non-zero, ``from``'s `tokenId` will be locked. - * - */ - function _afterTokenLock( - address operator, - address owner, - uint256 tokenId, - uint256 expired - ) internal virtual {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return interfaceId == type(IERC5058).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5058/IERC5058.sol b/assets/eip-5058/IERC5058.sol deleted file mode 100644 index 4f8846e..0000000 --- a/assets/eip-5058/IERC5058.sol +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @dev ERC-721 Non-Fungible Token Standard, optional lockable extension - * ERC721 Token that can be locked for a certain period and cannot be transferred. - * This is designed for a non-escrow staking contract that comes later to lock a user's NFT - * while still letting them keep it in their wallet. - * This extension can ensure the security of user tokens during the staking period. - * If the nft lending protocol is compatible with this extension, the trouble caused by the NFT - * airdrop can be avoided, because the airdrop is still in the user's wallet - */ -interface IERC5058 is IERC721 { - /** - * @dev Emitted when `tokenId` token is locked by `operator` from `owner`. - */ - event Locked(address indexed operator, address indexed owner, uint256 indexed tokenId, uint256 expired); - - /** - * @dev Emitted when `tokenId` token is unlocked by `operator` from `owner`. - */ - event Unlocked(address indexed operator, address indexed owner, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables `approved` to lock the `tokenId` token. - */ - event LockApproval(address indexed owner, address indexed approved, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables or disables (`approved`) `operator` to lock all of its tokens. - */ - event LockApprovalForAll(address indexed owner, address indexed operator, bool approved); - - /** - * @dev Returns the locker who is locking the `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function lockerOf(uint256 tokenId) external view returns (address locker); - - /** - * @dev Lock `tokenId` token until the block number is greater than `expired` to be unlocked. - * - * Requirements: - * - * - `tokenId` token must be owned by `owner`. - * - `expired` must be greater than block.number - * - If the caller is not `from`, it must be approved to lock this token - * by either {lockApprove} or {setLockApprovalForAll}. - * - * Emits a {Locked} event. - */ - function lock(uint256 tokenId, uint256 expired) external; - - /** - * @dev Unlock `tokenId` token. - * - * Requirements: - * - * - `tokenId` token must be owned by `from`. - * - the caller must be the operator who locks the token by {lock} - * - * Emits a {Unlocked} event. - */ - function unlock(uint256 tokenId) external; - - /** - * @dev Gives permission to `to` to lock `tokenId` token. - * - * Requirements: - * - * - The caller must own the token or be an approved lock operator. - * - `tokenId` must exist. - * - * Emits an {LockApproval} event. - */ - function lockApprove(address to, uint256 tokenId) external; - - /** - * @dev Approve or remove `operator` as an lock operator for the caller. - * Operators can call {lock} for any token owned by the caller. - * - * Requirements: - * - * - The `operator` cannot be the caller. - * - * Emits an {LockApprovalForAll} event. - */ - function setLockApprovalForAll(address operator, bool approved) external; - - /** - * @dev Returns the account lock approved for `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function getLockApproved(uint256 tokenId) external view returns (address operator); - - /** - * @dev Returns if the `operator` is allowed to lock all of the assets of `owner`. - * - * See {setLockApprovalForAll} - */ - function isLockApprovedForAll(address owner, address operator) external view returns (bool); - - /** - * @dev Returns if the `tokenId` token is locked. - */ - function isLocked(uint256 tokenId) external view returns (bool); - - /** - * @dev Returns the `tokenId` token lock expired time. - */ - function lockExpiredTime(uint256 tokenId) external view returns (uint256); -} diff --git a/assets/eip-5058/extensions/ERC5058Bound.sol b/assets/eip-5058/extensions/ERC5058Bound.sol deleted file mode 100644 index e0eebb6..0000000 --- a/assets/eip-5058/extensions/ERC5058Bound.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../factory/IERC5058Factory.sol"; -import "../factory/IERC721Bound.sol"; -import "../ERC5058.sol"; - -abstract contract ERC5058Bound is ERC5058 { - address public bound; - - function _setFactory(address _factory) internal { - bound = IERC5058Factory(_factory).boundOf(address(this)); - } - - function _setBoundBaseTokenURI(string memory uri) internal { - IERC721Bound(bound).setBaseTokenURI(uri); - } - - function _setBoundContractURI(string memory uri) internal { - IERC721Bound(bound).setContractURI(uri); - } - - function burnBound(uint256 tokenId) external { - IERC721Bound(bound).burn(tokenId); - } - - // NOTE: - // - // this will be called when `lock` or `unlock` - function _afterTokenLock( - address operator, - address from, - uint256 tokenId, - uint256 expired - ) internal virtual override { - super._afterTokenLock(operator, from, tokenId, expired); - - if (bound != address(0)) { - if (expired != 0) { - // lock mint - if (operator != address(0)) { - IERC721Bound(bound).safeMint(msg.sender, tokenId, ""); - } - } else { - // unlock - if (IERC721Bound(bound).exists(tokenId)) { - IERC721Bound(bound).burn(tokenId); - } - } - } - } -} diff --git a/assets/eip-5058/factory/ERC5058Factory.sol b/assets/eip-5058/factory/ERC5058Factory.sol deleted file mode 100644 index 3fda931..0000000 --- a/assets/eip-5058/factory/ERC5058Factory.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC721Bound.sol"; -import "./IERC5058Factory.sol"; - -contract ERC5058Factory is IERC5058Factory { - address[] private _allBounds; - - // Mapping from preimage to bound - mapping(address => address) private _bounds; - - function allBoundsLength() public view virtual override returns (uint256) { - return _allBounds.length; - } - - function boundByIndex(uint256 index) public view virtual override returns (address) { - require(index < _allBounds.length, "ERC5058Factory: index out of bounds"); - - return _allBounds[index]; - } - - function existBound(address preimage) public view virtual override returns (bool) { - return _bounds[preimage] != address(0); - } - - function boundOf(address preimage) public view virtual override returns (address) { - require(existBound(preimage), "ERC5058Factory: query for nonexistent bound"); - return _bounds[preimage]; - } - - function boundDeploy(address preimage) public virtual override returns (address) { - require(!existBound(preimage), "ERC5058Factory: bound nft is already deployed"); - - return _deploy(preimage, keccak256(abi.encode(preimage)), "Bound"); - } - - function _deploy( - address preimage, - bytes32 salt, - bytes memory prefix - ) internal returns (address) { - IERC721Metadata collection = IERC721Metadata(preimage); - bytes memory code = type(ERC721Bound).creationCode; - bytes memory bytecode = abi.encodePacked( - code, - abi.encode( - preimage, - abi.encodePacked(prefix, " ", collection.name()), - abi.encodePacked(prefix, collection.symbol()) - ) - ); - - address addr; - assembly { - addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) - } - - emit DeployedBound(preimage, addr); - - _bounds[preimage] = addr; - _allBounds.push(addr); - - return addr; - } -} diff --git a/assets/eip-5058/factory/ERC721Bound.sol b/assets/eip-5058/factory/ERC721Bound.sol deleted file mode 100644 index 28e9f17..0000000 --- a/assets/eip-5058/factory/ERC721Bound.sol +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC721Bound.sol"; - -interface IPreimage { - /** - * @dev Returns if the `tokenId` token of preimage is locked. [MUST] - */ - function isLocked(uint256 tokenId) external view returns (bool); - - /** - * @dev Opensea-contract-level metadata. [OPTIONAL] - * Details: https://docs.opensea.io/docs/contract-level-metadata - */ - function contractURI() external view returns (string memory); -} - -/** - * @dev This implements an optional extension of {ERC5058} defined in the EIP. - * The bound token is exactly the same as the locked token metadata, the bound token can be transferred, - * but it is guaranteed that only one bound token and the original token can be traded in the market at - * the same time. When the original token lock expires, the bound token must be destroyed. - */ -contract ERC721Bound is ERC721Enumerable, IERC2981, IERC721Bound { - address private _preimage; - - string private _contractURI; - - string private _baseTokenURI; - - constructor( - address preimage_, - string memory name_, - string memory symbol_ - ) ERC721(name_, symbol_) { - _preimage = preimage_; - } - - /** - * @dev Throws if called by any account other than the preimage. - */ - modifier onlyPreimage() { - require(_preimage == msg.sender, "ERC721Bound: caller is not the preimage"); - _; - } - - function preimage() public view virtual override returns (address) { - return _preimage; - } - - /** - * @dev See {ERC721-_baseURI}. - */ - function _baseURI() internal view virtual override returns (string memory) { - return _baseTokenURI; - } - - /** - * @dev See {IERC721Metadata-tokenURI}. - */ - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - if (bytes(_baseTokenURI).length > 0) { - return super.tokenURI(tokenId); - } - - return IERC721Metadata(_preimage).tokenURI(tokenId); - } - - /** - * @dev See {IERC2981-royaltyInfo}. - */ - function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual override returns (address, uint256) { - return IERC2981(_preimage).royaltyInfo(tokenId, salePrice); - } - - /** - * @dev See {IPreimage-contractURI}. - */ - function contractURI() public view returns (string memory) { - if (bytes(_contractURI).length > 0) { - return _contractURI; - } - - if (IERC165(_preimage).supportsInterface(IPreimage.contractURI.selector)) { - return IPreimage(_preimage).contractURI(); - } - - return ""; - } - - /** - * @dev Returns whether `tokenId` exists. - */ - function exists(uint256 tokenId) public view returns (bool) { - return _exists(tokenId); - } - - // @dev Sets the base token URI prefix. - function setBaseTokenURI(string memory baseTokenURI) public virtual override onlyPreimage { - _baseTokenURI = baseTokenURI; - } - - // @dev Sets the contract URI. - function setContractURI(string memory uri) public virtual override onlyPreimage { - _contractURI = uri; - } - - /** - * @dev Mints bound `tokenId` and transfers it to `to`. - * - * Requirements: - * - * - `tokenId` must not exist. - * - `to` cannot be the zero address. - * caller must be preimage contract. - * - * Emits a {Transfer} event. - */ - function safeMint( - address to, - uint256 tokenId, - bytes memory data - ) public virtual override onlyPreimage { - _safeMint(to, tokenId, data); - } - - /** - * @dev Destroys `tokenId`. - * The approval is cleared when the token is burned. - * - * Requirements: - * - * - `tokenId` must exist. - * caller must be preimage contract. - * - * Emits a {Transfer} event. - */ - function burn(uint256 tokenId) public virtual override onlyPreimage { - _burn(tokenId); - } - - /** - * @dev See {ERC721-_beforeTokenTransfer}. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._beforeTokenTransfer(from, to, tokenId); - - if (from == address(0)) { - require(IPreimage(_preimage).isLocked(tokenId), "ERC721Bound: token mint while preimage not locked"); - } - if (to == address(0)) { - require(!IPreimage(_preimage).isLocked(tokenId), "ERC721Bound: token burn while preimage locked"); - } - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(IERC165, ERC721Enumerable) - returns (bool) - { - return - interfaceId == type(IERC721Bound).interfaceId || - interfaceId == type(IERC2981).interfaceId || - interfaceId == IPreimage.contractURI.selector || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5058/factory/IERC5058Factory.sol b/assets/eip-5058/factory/IERC5058Factory.sol deleted file mode 100644 index 61ecb19..0000000 --- a/assets/eip-5058/factory/IERC5058Factory.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5058Factory { - event DeployedBound(address indexed preimage, address bound); - - function allBoundsLength() external view returns (uint256); - - function boundByIndex(uint256 index) external view returns (address); - - function existBound(address preimage) external view returns (bool); - - function boundOf(address preimage) external view returns (address); - - function boundDeploy(address preimage) external returns (address); -} diff --git a/assets/eip-5058/factory/IERC721Bound.sol b/assets/eip-5058/factory/IERC721Bound.sol deleted file mode 100644 index c8b86a9..0000000 --- a/assets/eip-5058/factory/IERC721Bound.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC721Bound is IERC721 { - function preimage() external view returns (address); - - function contractURI() external view returns (string memory); - - function exists(uint256 tokenId) external view returns (bool); - - function setBaseTokenURI(string memory _baseTokenURI) external; - - function setContractURI(string memory uri) external; - - function safeMint( - address to, - uint256 tokenId, - bytes memory data - ) external; - - function burn(uint256 tokenId) external; -} diff --git a/assets/eip-5058/mock/EIP5058Mock.sol b/assets/eip-5058/mock/EIP5058Mock.sol deleted file mode 100644 index 0d02a74..0000000 --- a/assets/eip-5058/mock/EIP5058Mock.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5058.sol"; - -contract EIP5058Mock is ERC721Enumerable, ERC5058 { - constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - - function exists(uint256 tokenId) public view returns (bool) { - return _exists(tokenId); - } - - function lockMint( - address to, - uint256 tokenId, - uint256 expired - ) external { - _safeLockMint(to, tokenId, expired, ""); - } - - function mint(address to, uint256 tokenId) external { - _mint(to, tokenId); - } - - function burn(uint256 tokenId) external { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not owner nor approved"); - - _burn(tokenId); - } - - function _burn(uint256 tokenId) internal virtual override(ERC721, ERC5058) { - super._burn(tokenId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override(ERC721Enumerable, ERC5058) { - super._beforeTokenTransfer(from, to, tokenId); - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721Enumerable, ERC5058) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5058/test/test.ts b/assets/eip-5058/test/test.ts deleted file mode 100644 index 4b72864..0000000 --- a/assets/eip-5058/test/test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import "@nomiclabs/hardhat-ethers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { EIP5058Mock } from "typechain-types"; - -describe("ERC5058 contract", function() { - let owner: SignerWithAddress; - let alice: SignerWithAddress; - let EIP5058: EIP5058Mock; - - beforeEach(async () => { - [owner, alice] = await ethers.getSigners(); - - const ERC5058Factory = await ethers.getContractFactory("EIP5058Mock"); - - EIP5058 = await ERC5058Factory.deploy("ERC5058Mock", "ERC5058"); - }); - - it("Deployment should assign the total supply of tokens to the owner", async function() { - const ownerBalance = await EIP5058.balanceOf(owner.address); - expect(await EIP5058.totalSupply()).to.equal(ownerBalance); - }); - - it("lockMint works", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(alice.address, NFTId, block + 2); - - expect(await EIP5058.lockExpiredTime(NFTId)).eq(block + 2); - expect(await EIP5058.isLocked(NFTId)).eq(true); - expect(await EIP5058.lockerOf(NFTId)).eq(owner.address); - }); - - it("Can not transfer when token is locked", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 3); - - expect(await EIP5058.isLocked(NFTId)).eq(true); - // can not transfer when token is locked - await expect(EIP5058.transferFrom(owner.address, alice.address, NFTId)).to.be.revertedWith( - "ERC5058: token transfer while locked", - ); - - // can transfer when token is unlocked - await ethers.provider.send("evm_mine", []); - - expect(await EIP5058.isLocked(NFTId)).eq(false); - await EIP5058.transferFrom(owner.address, alice.address, NFTId); - expect(await EIP5058.ownerOf(NFTId)).eq(alice.address); - }); - - it("isLocked works", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 2); - - // isLocked works - expect(await EIP5058.isLocked(NFTId)).eq(true); - await ethers.provider.send("evm_mine", []); - expect(await EIP5058.isLocked(NFTId)).eq(false); - }); - - it("lock works", async function() { - const NFTId = 0; - let block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 3); - - expect(await EIP5058.isLocked(NFTId)).eq(true); - await expect(EIP5058.lock(NFTId, block + 5)).to.be.revertedWith( - "ERC5058: token is locked", - ); - - await ethers.provider.send("evm_mine", []); - expect(await EIP5058.isLocked(NFTId)).eq(false); - await EIP5058.lock(NFTId, block + 5); - }); - - it("unlock works with lockMint", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 3); - - // unlock works - expect(await EIP5058.isLocked(NFTId)).eq(true); - expect(await EIP5058.lockerOf(NFTId)).eq(owner.address); - await EIP5058.unlock(NFTId); - expect(await EIP5058.isLocked(NFTId)).eq(false); - }); - - it("unlock works", async function() { - const NFTId = 0; - - await EIP5058.mint(owner.address, NFTId); - await expect(EIP5058.unlock(NFTId)).to.be.revertedWith( - "ERC5058: locker query for non-locked token", - ); - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lock(NFTId, block + 3); - expect(await EIP5058.isLocked(NFTId)).eq(true); - await EIP5058.unlock(NFTId); - expect(await EIP5058.isLocked(NFTId)).eq(false); - }); - - it("lockApprove works", async function() { - const NFTId = 0; - await EIP5058.mint(alice.address, NFTId); - let block = await ethers.provider.getBlockNumber(); - - await expect(EIP5058.lock(NFTId, block + 4)).to.be.revertedWith( - "ERC5058: lock caller is not owner nor approved", - ); - await EIP5058.connect(alice).lockApprove(owner.address, NFTId); - expect(await EIP5058.getLockApproved(NFTId)).eq(owner.address); - - await EIP5058.lock(NFTId, block + 8); - expect(await EIP5058.isLocked(NFTId)).eq(true); - - await expect(EIP5058.lockApprove(alice.address, NFTId)).to.be.revertedWith( - "ERC5058: token is locked", - ); - }); - - it("setLockApproveForAll works", async function() { - const NFTId = 0; - - await EIP5058.mint(alice.address, NFTId); - const block = await ethers.provider.getBlockNumber(); - await expect(EIP5058.lock(NFTId, block + 2)).to.be.revertedWith( - "ERC5058: lock caller is not owner nor approved", - ); - - await EIP5058.connect(alice).setLockApprovalForAll(owner.address, true); - expect(await EIP5058.isLockApprovedForAll(alice.address, owner.address)).eq(true); - - await EIP5058.lock(NFTId, block + 6); - - await EIP5058.connect(alice).setLockApprovalForAll(owner.address, false); - expect(await EIP5058.isLockApprovedForAll(alice.address, owner.address)).eq(false); - }); -}); diff --git a/assets/eip-5139/AUTHORS.md b/assets/eip-5139/AUTHORS.md deleted file mode 100644 index 24cf6a2..0000000 --- a/assets/eip-5139/AUTHORS.md +++ /dev/null @@ -1,37 +0,0 @@ -SemVer Authors -============== - -The following people have modified the Semantic Versioning 2.0.0 specification: - - - Tom Preston-Werner - - Phil Haack - - Haacked - - isaacs - - Thijs Schreijer - - jeffhandley - - Alexandr Tovmach - - Adam Ralph - - Eddie Garmon - - Jeff Handley - - Krzysztof Piasecki - - Doug Beck - - Gert de Pagter - - Guillermo Calvo - - Iulian Onofrei - - Ivan Bessarabov - - Jo Liss - - Johanan Liebermann - - Joseph Donahue - - Konstantin - - Kristian Glass - - Mark Amery - - OGINO Masanori - - Oguz Bilgic - - Slipp Douglas - - Thomas Schraitle - - Tim Vergenz - - Todd Reed - - Tristram Oaten - - Wincent Colaiuta - - alexandrtovmach - - wolf99 diff --git a/assets/eip-5139/semver.md b/assets/eip-5139/semver.md deleted file mode 100644 index 95cf203..0000000 --- a/assets/eip-5139/semver.md +++ /dev/null @@ -1,373 +0,0 @@ -Semantic Versioning 2.0.0 -============================== - -Summary -------- - -Given a version number MAJOR.MINOR.PATCH, increment the: - -1. MAJOR version when you make incompatible API changes, -1. MINOR version when you add functionality in a backwards compatible - manner, and -1. PATCH version when you make backwards compatible bug fixes. - -Additional labels for pre-release and build metadata are available as extensions -to the MAJOR.MINOR.PATCH format. - -Introduction ------------- - -In the world of software management there exists a dreaded place called -"dependency hell." The bigger your system grows and the more packages you -integrate into your software, the more likely you are to find yourself, one -day, in this pit of despair. - -In systems with many dependencies, releasing new package versions can quickly -become a nightmare. If the dependency specifications are too tight, you are in -danger of version lock (the inability to upgrade a package without having to -release new versions of every dependent package). If dependencies are -specified too loosely, you will inevitably be bitten by version promiscuity -(assuming compatibility with more future versions than is reasonable). -Dependency hell is where you are when version lock and/or version promiscuity -prevent you from easily and safely moving your project forward. - -As a solution to this problem, we propose a simple set of rules and -requirements that dictate how version numbers are assigned and incremented. -These rules are based on but not necessarily limited to pre-existing -widespread common practices in use in both closed and open-source software. -For this system to work, you first need to declare a public API. This may -consist of documentation or be enforced by the code itself. Regardless, it is -important that this API be clear and precise. Once you identify your public -API, you communicate changes to it with specific increments to your version -number. Consider a version format of X.Y.Z (Major.Minor.Patch). Bug fixes not -affecting the API increment the patch version, backwards compatible API -additions/changes increment the minor version, and backwards incompatible API -changes increment the major version. - -We call this system "Semantic Versioning." Under this scheme, version numbers -and the way they change convey meaning about the underlying code and what has -been modified from one version to the next. - -Semantic Versioning Specification (SemVer) ------------------------------------------- - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", -"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be -interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119). - -1. Software using Semantic Versioning MUST declare a public API. This API -could be declared in the code itself or exist strictly in documentation. -However it is done, it SHOULD be precise and comprehensive. - -1. A normal version number MUST take the form X.Y.Z where X, Y, and Z are -non-negative integers, and MUST NOT contain leading zeroes. X is the -major version, Y is the minor version, and Z is the patch version. -Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0. - -1. Once a versioned package has been released, the contents of that version -MUST NOT be modified. Any modifications MUST be released as a new version. - -1. Major version zero (0.y.z) is for initial development. Anything MAY change -at any time. The public API SHOULD NOT be considered stable. - -1. Version 1.0.0 defines the public API. The way in which the version number -is incremented after this release is dependent on this public API and how it -changes. - -1. Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards -compatible bug fixes are introduced. A bug fix is defined as an internal -change that fixes incorrect behavior. - -1. Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards -compatible functionality is introduced to the public API. It MUST be -incremented if any public API functionality is marked as deprecated. It MAY be -incremented if substantial new functionality or improvements are introduced -within the private code. It MAY include patch level changes. Patch version -MUST be reset to 0 when minor version is incremented. - -1. Major version X (X.y.z | X > 0) MUST be incremented if any backwards -incompatible changes are introduced to the public API. It MAY also include minor -and patch level changes. Patch and minor versions MUST be reset to 0 when major -version is incremented. - -1. A pre-release version MAY be denoted by appending a hyphen and a -series of dot separated identifiers immediately following the patch -version. Identifiers MUST comprise only ASCII alphanumerics and hyphens -[0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers MUST -NOT include leading zeroes. Pre-release versions have a lower -precedence than the associated normal version. A pre-release version -indicates that the version is unstable and might not satisfy the -intended compatibility requirements as denoted by its associated -normal version. Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, -1.0.0-x.7.z.92, 1.0.0-x-y-z.--. - -1. Build metadata MAY be denoted by appending a plus sign and a series of dot -separated identifiers immediately following the patch or pre-release version. -Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]. -Identifiers MUST NOT be empty. Build metadata MUST be ignored when determining -version precedence. Thus two versions that differ only in the build metadata, -have the same precedence. Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, -1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3----117B344092BD. - -1. Precedence refers to how versions are compared to each other when ordered. - - 1. Precedence MUST be calculated by separating the version into major, - minor, patch and pre-release identifiers in that order (Build metadata - does not figure into precedence). - - 1. Precedence is determined by the first difference when comparing each of - these identifiers from left to right as follows: Major, minor, and patch - versions are always compared numerically. - - Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1. - - 1. When major, minor, and patch are equal, a pre-release version has lower - precedence than a normal version: - - Example: 1.0.0-alpha < 1.0.0. - - 1. Precedence for two pre-release versions with the same major, minor, and - patch version MUST be determined by comparing each dot separated identifier - from left to right until a difference is found as follows: - - 1. Identifiers consisting of only digits are compared numerically. - - 1. Identifiers with letters or hyphens are compared lexically in ASCII - sort order. - - 1. Numeric identifiers always have lower precedence than non-numeric - identifiers. - - 1. A larger set of pre-release fields has a higher precedence than a - smaller set, if all of the preceding identifiers are equal. - - Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < - 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0. - -Backus–Naur Form Grammar for Valid SemVer Versions --------------------------------------------------- -``` - ::= - | "-" - | "+" - | "-" "+" - - ::= "." "." - - ::= - - ::= - - ::= - - ::= - - ::= - | "." - - ::= - - ::= - | "." - - ::= - | - - ::= - | - - ::= - | - | - | - - ::= "0" - | - | - - ::= - | - - ::= - | - - ::= - | "-" - - ::= - | - - ::= "0" - | - - ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" - - ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" - | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" - | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" - | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" - | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" - | "y" | "z" -``` - -Why Use Semantic Versioning? ----------------------------- - -This is not a new or revolutionary idea. In fact, you probably do something -close to this already. The problem is that "close" isn't good enough. Without -compliance to some sort of formal specification, version numbers are -essentially useless for dependency management. By giving a name and clear -definition to the above ideas, it becomes easy to communicate your intentions -to the users of your software. Once these intentions are clear, flexible (but -not too flexible) dependency specifications can finally be made. - -A simple example will demonstrate how Semantic Versioning can make dependency -hell a thing of the past. Consider a library called "Firetruck." It requires a -Semantically Versioned package named "Ladder." At the time that Firetruck is -created, Ladder is at version 3.1.0. Since Firetruck uses some functionality -that was first introduced in 3.1.0, you can safely specify the Ladder -dependency as greater than or equal to 3.1.0 but less than 4.0.0. Now, when -Ladder version 3.1.1 and 3.2.0 become available, you can release them to your -package management system and know that they will be compatible with existing -dependent software. - -As a responsible developer you will, of course, want to verify that any -package upgrades function as advertised. The real world is a messy place; -there's nothing we can do about that but be vigilant. What you can do is let -Semantic Versioning provide you with a sane way to release and upgrade -packages without having to roll new versions of dependent packages, saving you -time and hassle. - -If all of this sounds desirable, all you need to do to start using Semantic -Versioning is to declare that you are doing so and then follow the rules. Link -to this website from your README so others know the rules and can benefit from -them. - -FAQ ---- - -### How should I deal with revisions in the 0.y.z initial development phase? - -The simplest thing to do is start your initial development release at 0.1.0 -and then increment the minor version for each subsequent release. - -### How do I know when to release 1.0.0? - -If your software is being used in production, it should probably already be -1.0.0. If you have a stable API on which users have come to depend, you should -be 1.0.0. If you're worrying a lot about backwards compatibility, you should -probably already be 1.0.0. - -### Doesn't this discourage rapid development and fast iteration? - -Major version zero is all about rapid development. If you're changing the API -every day you should either still be in version 0.y.z or on a separate -development branch working on the next major version. - -### If even the tiniest backwards incompatible changes to the public API require a major version bump, won't I end up at version 42.0.0 very rapidly? - -This is a question of responsible development and foresight. Incompatible -changes should not be introduced lightly to software that has a lot of -dependent code. The cost that must be incurred to upgrade can be significant. -Having to bump major versions to release incompatible changes means you'll -think through the impact of your changes, and evaluate the cost/benefit ratio -involved. - -### Documenting the entire public API is too much work! - -It is your responsibility as a professional developer to properly document -software that is intended for use by others. Managing software complexity is a -hugely important part of keeping a project efficient, and that's hard to do if -nobody knows how to use your software, or what methods are safe to call. In -the long run, Semantic Versioning, and the insistence on a well defined public -API can keep everyone and everything running smoothly. - -### What do I do if I accidentally release a backwards incompatible change as a minor version? - -As soon as you realize that you've broken the Semantic Versioning spec, fix -the problem and release a new minor version that corrects the problem and -restores backwards compatibility. Even under this circumstance, it is -unacceptable to modify versioned releases. If it's appropriate, -document the offending version and inform your users of the problem so that -they are aware of the offending version. - -### What should I do if I update my own dependencies without changing the public API? - -That would be considered compatible since it does not affect the public API. -Software that explicitly depends on the same dependencies as your package -should have their own dependency specifications and the author will notice any -conflicts. Determining whether the change is a patch level or minor level -modification depends on whether you updated your dependencies in order to fix -a bug or introduce new functionality. We would usually expect additional code -for the latter instance, in which case it's obviously a minor level increment. - -### What if I inadvertently alter the public API in a way that is not compliant with the version number change (i.e. the code incorrectly introduces a major breaking change in a patch release)? - -Use your best judgment. If you have a huge audience that will be drastically -impacted by changing the behavior back to what the public API intended, then -it may be best to perform a major version release, even though the fix could -strictly be considered a patch release. Remember, Semantic Versioning is all -about conveying meaning by how the version number changes. If these changes -are important to your users, use the version number to inform them. - -### How should I handle deprecating functionality? - -Deprecating existing functionality is a normal part of software development and -is often required to make forward progress. When you deprecate part of your -public API, you should do two things: (1) update your documentation to let -users know about the change, (2) issue a new minor release with the deprecation -in place. Before you completely remove the functionality in a new major release -there should be at least one minor release that contains the deprecation so -that users can smoothly transition to the new API. - -### Does SemVer have a size limit on the version string? - -No, but use good judgment. A 255 character version string is probably overkill, -for example. Also, specific systems may impose their own limits on the size of -the string. - -### Is "v1.2.3" a semantic version? - -No, "v1.2.3" is not a semantic version. However, prefixing a semantic version -with a "v" is a common way (in English) to indicate it is a version number. -Abbreviating "version" as "v" is often seen with version control. Example: -`git tag v1.2.3 -m "Release version 1.2.3"`, in which case "v1.2.3" is a tag -name and the semantic version is "1.2.3". - -### Is there a suggested regular expression (RegEx) to check a SemVer string? - -There are two. One with named groups for those systems that support them -(PCRE [Perl Compatible Regular Expressions, i.e. Perl, PHP and R], Python -and Go). - -See: - -``` -^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ -``` - -And one with numbered capture groups instead (so cg1 = major, cg2 = minor, -cg3 = patch, cg4 = prerelease and cg5 = buildmetadata) that is compatible -with ECMA Script (JavaScript), PCRE (Perl Compatible Regular Expressions, -i.e. Perl, PHP and R), Python and Go. - -See: - -``` -^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ -``` - -About ------ - -The Semantic Versioning specification was originally authored by [Tom -Preston-Werner](https://tom.preston-werner.com), inventor of Gravatar and -cofounder of GitHub. - -If you'd like to leave feedback, please [open an issue on -GitHub](https://github.com/semver/semver/issues). - -License -------- - -[Creative Commons ― CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) diff --git a/assets/eip-5169/contract/ExampleContract.sol b/assets/eip-5169/contract/ExampleContract.sol deleted file mode 100644 index 298c1c2..0000000 --- a/assets/eip-5169/contract/ExampleContract.sol +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -library AddressUtil { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize, which returns 0 for contracts in - // construction, since the code is only stored at the end of the - // constructor execution. - - uint256 size; - // solhint-disable-next-line no-inline-assembly - assembly { size := extcodesize(account) } - return size > 0; - } -} - -abstract contract MultiOwnable is Ownable { - mapping(address => bool) private _admins; - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - constructor() Ownable() { - _admins[_msgSender()] = true; - } - - function addAdmin(address newAdmin) public onlyOwner { - _admins[newAdmin] = true; - } - - function revokeAdmin(address currentAdmin) public onlyOwner { - delete _admins[currentAdmin]; - } - - function isAdmin(address sender) public view returns(bool) { - return _admins[sender]; - } - - /** - * @dev Throws if called by a non-admin - */ - modifier onlyAdmins() { - require(_admins[_msgSender()] == true, "Ownable: caller is not an admin"); - _; - } -} - -interface IERC5169 { - /// @dev This event emits when the scriptURI is updated, - /// so wallets implementing this interface can update a cached script - event ScriptUpdate(string newScriptURI); - - /// @notice Get the scriptURI for the contract - /// @return The scriptURI - function scriptURI() external view returns(string memory); - - /// @notice Update the scriptURI - /// emits event ScriptUpdate(string memory newScriptURI); - function updateScriptURI(string memory newScriptURI) external; -} - -contract STLDoor is ERC721, MultiOwnable, IERC5169 { - using AddressUtil for address; - using Strings for uint256; - using Counters for Counters.Counter; - - Counters.Counter public _tokenIdCounter; - Counters.Counter public _topTokenIdCounter; - Counters.Counter public _stlTokenIdCounter; - - uint256 private constant _topTokenId = 10000; - - string private _scriptURI; - - constructor() ERC721("STL HQ Door", "OFFICE") { - _tokenIdCounter.increment(); - _scriptURI = "ipfs://QmXXLFBeSjXAwAhbo1344wJSjLgoUrfUK9LE57oVubaRRp"; - mintUsingSequentialTokenId(); - } - - function contractURI() public pure returns (string memory) { - return "ipfs://QmUgdLvPvjuHGfMsuK1H2jFpg5r1QNc8JeWyXyRwKP8pTf"; - } - - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - require(_exists(tokenId), "tokenURI: URI query for nonexistent token"); - if (tokenId < _topTokenId) { - return "ipfs://QmW948aN4Tjh4eLkAAo8os1AcM2FJjA46qtaEfFAnyNYzY"; - } else if (tokenId < _topTokenId * 2) { - return "ipfs://QmR31f2AUokC5QyLXzDYUjy5tVibkjbW4voVuMBZfrNVU8"; - } else { - return "ipfs://QmdaSTaF6WXpYWiL5ck7csmTy5EWHzYVGykJZN7TR95dSS"; - } - } - - function scriptURI() public view override returns (string memory) { - return _scriptURI; - } - - function updateScriptURI(string memory newScriptURI) public override onlyAdmins { - _scriptURI = newScriptURI; - emit ScriptUpdate(newScriptURI); - } - - function mintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) { - tokenId = _tokenIdCounter.current(); - require(tokenId < _topTokenId, "Hit upper mint limit"); - _mint(msg.sender, tokenId); - _tokenIdCounter.increment(); - } - - function topMintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) { - tokenId = _topTokenIdCounter.current() + _topTokenId; - require(tokenId < _topTokenId*2, "Hit upper mint limit"); - _mint(msg.sender, tokenId); - _topTokenIdCounter.increment(); - } - - function stlMintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) { - tokenId = _stlTokenIdCounter.current() + _topTokenId*2; - _mint(msg.sender, tokenId); - _stlTokenIdCounter.increment(); - } - - function burnToken(uint256 tokenId) public onlyAdmins { - require(_exists(tokenId), "burn: nonexistent token"); - _burn(tokenId); - } - - // Only allow owners to transfer tokens - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) public override onlyAdmins { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); - _safeTransfer(from, to, tokenId, _data); - } - - function transferFrom( - address from, - address to, - uint256 tokenId - ) public override onlyAdmins { - //solhint-disable-next-line max-line-length - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); - - _transfer(from, to, tokenId); - } - - function selfDestruct() public payable onlyOwner { - selfdestruct(payable(owner())); - } -} diff --git a/assets/eip-5169/tokenscript/ExampleScript.xml b/assets/eip-5169/tokenscript/ExampleScript.xml deleted file mode 100644 index 5fb54cc..0000000 --- a/assets/eip-5169/tokenscript/ExampleScript.xml +++ /dev/null @@ -1,421 +0,0 @@ - - - - - STL Office Token - STL Office Tokens - - - Boleto de admisión - Boleto de admisiónes - - - 入場券 - 入場券 - - - - 0xB424e50674a38e83c7Eca39945fe5B45B4cd3705 - - - - - - - - - - - Unlock - 开锁 - Abrir - - - - - - - - - - Lock - 关锁 - Cerrar - - - - - - - - - - Mint Ape 1 - - - - - - - - - - - - - - - Mint Ape 2 - - - - - - - - - - - - - - - Mint STL Token - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/eip-5173/Arithmetic_Sequence_FR_Payout_Distribution.png b/assets/eip-5173/Arithmetic_Sequence_FR_Payout_Distribution.png deleted file mode 100644 index 5ebce00..0000000 Binary files a/assets/eip-5173/Arithmetic_Sequence_FR_Payout_Distribution.png and /dev/null differ diff --git a/assets/eip-5173/Implementation/InFR.sol b/assets/eip-5173/Implementation/InFR.sol deleted file mode 100644 index 18fa1e5..0000000 --- a/assets/eip-5173/Implementation/InFR.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/* - * - * @dev Interface for the Future Rewards Token Standard. - * - * A standardized way to receive future rewards for non-fungible tokens (NFTs.) - * - */ -interface InFR is IERC165 { - - event FRClaimed(address indexed account, uint256 indexed amount); - - event FRDistributed(uint256 indexed tokenId, uint256 indexed soldPrice, uint256 indexed allocatedFR); - - function list(uint256 tokenId, uint256 salePrice) external; - - function unlist(uint256 tokenId) external; - - function buy(uint256 tokenId) payable external; - - function releaseFR(address payable account) external; - - function retrieveFRInfo(uint256 tokenId) external returns(uint8, uint256, uint256, uint256, uint256, address[] memory); - - function retrieveAllottedFR(address account) external returns(uint256); - - function retrieveListInfo(uint256 tokenId) external returns(uint256, address, bool); - -} diff --git a/assets/eip-5173/Implementation/nFR.sol b/assets/eip-5173/Implementation/nFR.sol deleted file mode 100644 index b02a483..0000000 --- a/assets/eip-5173/Implementation/nFR.sol +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./InFR.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@prb/math/contracts/PRBMathUD60x18.sol"; -import "@prb/math/contracts/PRBMathSD59x18.sol"; - -import "hardhat/console.sol"; - -abstract contract nFR is InFR, ERC721 { - - using Address for address; - - using PRBMathUD60x18 for uint256; - using PRBMathSD59x18 for int256; - - struct FRInfo { - uint8 numGenerations; // Number of generations corresponding to that Token ID - uint256 percentOfProfit; // Percent of profit allocated for FR, scaled by 1e18 - uint256 successiveRatio; // The common ratio of successive in the geometric sequence, used for distribution calculation - uint256 lastSoldPrice; // Last sale price in ETH mantissa - uint256 ownerAmount; // Amount of owners the Token ID has seen - bool isValid; // Updated by contract and signifies if an FR Info for a given Token ID is valid - } - - struct ListInfo { - uint256 salePrice; // ETH mantissa of the listed selling price - address lister; // Owner/Lister of the Token - bool isListed; // Boolean indicating whether the Token is listed or not - } - - FRInfo private _defaultFRInfo; - - // Takes Token ID and returns corresponding FR Info - mapping(uint256 => FRInfo) private _tokenFRInfo; - - // Takes Token ID and returns the addresses currently in the FR cycle - mapping(uint256 => address[]) private _addressesInFR; - - // Takes Address and returns amount of ether available to address from FR payments - mapping(address => uint256) private _allottedFR; - - // Takes Token ID and returns corresponding ListInfo - mapping(uint256 => ListInfo) private _tokenListInfo; - - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return interfaceId == type(InFR).interfaceId || super.supportsInterface(interfaceId); - } - - function retrieveFRInfo(uint256 tokenId) public view virtual override returns(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio, uint256 lastSoldPrice, uint256 ownerAmount, address[] memory addressesInFR) { - return (_tokenFRInfo[tokenId].numGenerations, _tokenFRInfo[tokenId].percentOfProfit, _tokenFRInfo[tokenId].successiveRatio, _tokenFRInfo[tokenId].lastSoldPrice, _tokenFRInfo[tokenId].ownerAmount, _addressesInFR[tokenId]); - } - - function retrieveListInfo(uint256 tokenId) public view virtual override returns(uint256, address, bool) { - return (_tokenListInfo[tokenId].salePrice, _tokenListInfo[tokenId].lister, _tokenListInfo[tokenId].isListed); - } - - function retrieveAllottedFR(address account) public view virtual override returns(uint256) { - return _allottedFR[account]; - } - - function _transferFrom(address from, address to, uint256 tokenId, uint256 soldPrice) internal virtual { - ERC721._transfer(from, to, tokenId); - require(_checkERC721Received(from, to, tokenId, ""), "ERC721: transfer to non ERC721Receiver implementer"); - - if (soldPrice <= _tokenFRInfo[tokenId].lastSoldPrice) { // NFT sold for a loss, meaning no FR distribution, but we still shift generations, and update price. We return ALL of the received ETH to the msg.sender as no FR chunk was needed. - _tokenFRInfo[tokenId].lastSoldPrice = soldPrice; - _tokenFRInfo[tokenId].ownerAmount++; - _shiftGenerations(to, tokenId); - (bool sent, ) = payable(_tokenListInfo[tokenId].lister).call{value: soldPrice}(""); - require(sent, "ERC5173: Failed to send msg.value to lister"); - } else { - _distributeFR(tokenId, soldPrice); - _tokenFRInfo[tokenId].lastSoldPrice = soldPrice; - _tokenFRInfo[tokenId].ownerAmount++; - _shiftGenerations(to, tokenId); - } - - delete _tokenListInfo[tokenId]; - } - - function list(uint256 tokenId, uint256 salePrice) public virtual override { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC5173: list caller is not owner nor approved"); - - _tokenListInfo[tokenId] = ListInfo(salePrice, _msgSender(), true); - } - - function unlist(uint256 tokenId) public virtual override { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC5173: unlist caller is not owner nor approved"); - - delete _tokenListInfo[tokenId]; - } - - function buy(uint256 tokenId) public virtual override payable { - require(_tokenListInfo[tokenId].isListed == true, "Token is not listed"); - require(_tokenListInfo[tokenId].salePrice == msg.value, "salePrice and msg.value mismatch"); - - _transferFrom(_tokenListInfo[tokenId].lister, _msgSender(), tokenId, _tokenListInfo[tokenId].salePrice); - } - - function _transfer(address from, address to, uint256 tokenId) internal virtual override { - super._transfer(from, to, tokenId); - - if (_tokenListInfo[tokenId].isListed == true) { - delete _tokenListInfo[tokenId]; - } - - _tokenFRInfo[tokenId].lastSoldPrice = 0; - _tokenFRInfo[tokenId].ownerAmount++; - _shiftGenerations(to, tokenId); - } - - function _mint(address to, uint256 tokenId) internal virtual override { - require(_defaultFRInfo.isValid, "No Default FR Info has been set"); - - super._mint(to, tokenId); - - _tokenFRInfo[tokenId] = FRInfo(_defaultFRInfo.numGenerations, _defaultFRInfo.percentOfProfit, _defaultFRInfo.successiveRatio, 0, 1, true); - - _addressesInFR[tokenId].push(to); - } - - function _burn(uint256 tokenId) internal virtual override { - super._burn(tokenId); - - delete _tokenFRInfo[tokenId]; - delete _addressesInFR[tokenId]; - delete _tokenListInfo[tokenId]; - } - - function _mint(address to, uint256 tokenId, uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) internal virtual { - require(numGenerations > 0 && percentOfProfit > 0 && percentOfProfit <= 1e18 && successiveRatio > 0, "Invalid Data Passed"); - - ERC721._mint(to, tokenId); - require(_checkERC721Received(address(0), to, tokenId, ""), "ERC721: transfer to non ERC721Receiver implementer"); - - _tokenFRInfo[tokenId] = FRInfo(numGenerations, percentOfProfit, successiveRatio, 0, 1, true); - - _addressesInFR[tokenId].push(to); - } - - function _distributeFR(uint256 tokenId, uint256 soldPrice) internal virtual { - uint256 profit = soldPrice - _tokenFRInfo[tokenId].lastSoldPrice; - uint256[] memory FR = _calculateFR(profit, _tokenFRInfo[tokenId].percentOfProfit, _tokenFRInfo[tokenId].successiveRatio, _tokenFRInfo[tokenId].ownerAmount, _tokenFRInfo[tokenId].numGenerations); - - for (uint owner = 0; owner < FR.length; owner++) { - _allottedFR[_addressesInFR[tokenId][owner]] += FR[owner]; - } - - uint256 allocatedFR = 0; - - for (uint reward = 0; reward < FR.length; reward++) { - allocatedFR += FR[reward]; - } - - (bool sent, ) = payable(_tokenListInfo[tokenId].lister).call{value: soldPrice - allocatedFR}(""); - require(sent, "Failed to send ETH after FR distribution to lister"); - - emit FRDistributed(tokenId, soldPrice, allocatedFR); - } - - function _shiftGenerations(address to, uint256 tokenId) internal virtual { - if (_addressesInFR[tokenId].length < _tokenFRInfo[tokenId].numGenerations) { // We just want to push to the array - _addressesInFR[tokenId].push(to); - } else { // We want to remove the first element in the array and then push to the end of the array - for (uint i = 0; i < _addressesInFR[tokenId].length-1; i++) { - _addressesInFR[tokenId][i] = _addressesInFR[tokenId][i+1]; - } - - _addressesInFR[tokenId].pop(); - - _addressesInFR[tokenId].push(to); - } - } - - function _setDefaultFRInfo(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) internal virtual { - require(numGenerations > 0 && percentOfProfit > 0 && percentOfProfit <= 1e18 && successiveRatio > 0, "Invalid Data Passed"); - - _defaultFRInfo.numGenerations = numGenerations; - _defaultFRInfo.percentOfProfit = percentOfProfit; - _defaultFRInfo.successiveRatio = successiveRatio; - _defaultFRInfo.isValid = true; - } - - function releaseFR(address payable account) public virtual override { - require(_allottedFR[account] > 0, "No FR Payment due"); - - uint256 FRAmount = _allottedFR[account]; - - _allottedFR[account] = 0; - - (bool sent, ) = account.call{value: FRAmount}(""); - require(sent, "Failed to release FR"); - - emit FRClaimed(account, FRAmount); - } - - function _calculateFR(uint256 totalProfit, uint256 buyerReward, uint256 successiveRatio, uint256 ownerAmount, uint256 windowSize) pure internal virtual returns(uint256[] memory) { - uint256 n = Math.min(ownerAmount, windowSize); - uint256[] memory FR = new uint256[](n); - - for (uint256 i = 1; i < n + 1; i++) { - uint256 pi = 0; - - if (successiveRatio != 1e18) { - int256 v1 = 1e18 - int256(successiveRatio).powu(n); - int256 v2 = int256(successiveRatio).powu(i - 1); - int256 v3 = int256(totalProfit).mul(int256(buyerReward)); - int256 v4 = v3.mul(1e18 - int256(successiveRatio)); - pi = uint256(v4 * v2 / v1); - } else { - pi = totalProfit.mul(buyerReward).div(n); - } - - FR[n - i] = pi; - } - - return FR; - } - - function _checkERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) private returns (bool) { - if (to.isContract()) { - try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert("ERC721: transfer to non ERC721Receiver implementer"); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - -} \ No newline at end of file diff --git a/assets/eip-5173/Implementation/nFRImplementation.sol b/assets/eip-5173/Implementation/nFRImplementation.sol deleted file mode 100644 index 5605121..0000000 --- a/assets/eip-5173/Implementation/nFRImplementation.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Contract based on [https://docs.openzeppelin.com/contracts/3.x/erc721](https://docs.openzeppelin.com/contracts/3.x/erc721) -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "./nFR.sol"; - -contract MyNFT is ERC721URIStorage, Ownable, nFR { - using Counters for Counters.Counter; - Counters.Counter private _tokenIds; - - constructor() ERC721("MyNFT", "NFT") {} - - function mintNFT(address recipient, uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio, string memory tokenURI) - public onlyOwner - returns (uint256) - { - _tokenIds.increment(); - - uint256 newItemId = _tokenIds.current(); - _mint(recipient, newItemId, numGenerations, percentOfProfit, successiveRatio); - _setTokenURI(newItemId, tokenURI); - - return newItemId; - } - - function mintERC721(address recipient, string memory tokenURI) public onlyOwner { - _tokenIds.increment(); - - uint256 newItemId = _tokenIds.current(); - _mint(recipient, newItemId); - _setTokenURI(newItemId, tokenURI); - } - - function setDefaultFRInfo(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) public onlyOwner { - _setDefaultFRInfo(numGenerations, percentOfProfit, successiveRatio); - } - - function burnNFT(uint256 tokenId) public onlyOwner { - _burn(tokenId); - } - - function _burn(uint256 tokenId) internal override(nFR, ERC721URIStorage) { - super._burn(tokenId); - } - - function _transfer(address from, address to, uint256 tokenId) internal override(ERC721, nFR) { - super._transfer(from, to, tokenId); - } - - function _mint(address to, uint256 tokenId) internal virtual override(ERC721, nFR) { - super._mint(to, tokenId); - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, nFR) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function tokenURI(uint256 tokenId) - public - view - override(ERC721, ERC721URIStorage) - returns (string memory) - { - return super.tokenURI(tokenId); - } -} diff --git a/assets/eip-5173/Losing_owners.jpeg b/assets/eip-5173/Losing_owners.jpeg deleted file mode 100644 index 26878c1..0000000 Binary files a/assets/eip-5173/Losing_owners.jpeg and /dev/null differ diff --git a/assets/eip-5173/Same_owner_using_different_wallets.jpeg b/assets/eip-5173/Same_owner_using_different_wallets.jpeg deleted file mode 100644 index 7cca2da..0000000 Binary files a/assets/eip-5173/Same_owner_using_different_wallets.jpeg and /dev/null differ diff --git a/assets/eip-5173/Total_FR_Payout_Distribution-flat.png b/assets/eip-5173/Total_FR_Payout_Distribution-flat.png deleted file mode 100644 index 8756b13..0000000 Binary files a/assets/eip-5173/Total_FR_Payout_Distribution-flat.png and /dev/null differ diff --git a/assets/eip-5173/Total_FR_Payout_Distribution-geo.png b/assets/eip-5173/Total_FR_Payout_Distribution-geo.png deleted file mode 100644 index e0b618b..0000000 Binary files a/assets/eip-5173/Total_FR_Payout_Distribution-geo.png and /dev/null differ diff --git a/assets/eip-5173/animate-1920x1080-1750-frames.gif b/assets/eip-5173/animate-1920x1080-1750-frames.gif deleted file mode 100644 index b6a7a88..0000000 Binary files a/assets/eip-5173/animate-1920x1080-1750-frames.gif and /dev/null differ diff --git a/assets/eip-5173/nFR_Standard_Outline.jpeg b/assets/eip-5173/nFR_Standard_Outline.jpeg deleted file mode 100644 index 1d0fc2a..0000000 Binary files a/assets/eip-5173/nFR_Standard_Outline.jpeg and /dev/null differ diff --git a/assets/eip-5173/nFR_distribution_formula.png b/assets/eip-5173/nFR_distribution_formula.png deleted file mode 100644 index b4f42ac..0000000 Binary files a/assets/eip-5173/nFR_distribution_formula.png and /dev/null differ diff --git a/assets/eip-5216/ERC1155ApprovalByAmount.sol b/assets/eip-5216/ERC1155ApprovalByAmount.sol deleted file mode 100644 index ee94e35..0000000 --- a/assets/eip-5216/ERC1155ApprovalByAmount.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.15; - -import "IERC1155.sol"; -import "ERC1155.sol"; - -/** - * @title ERC-1155 Approval By Amount Extension - * Note: the ERC-165 identifier for this interface is 0x1be07d74 - */ -interface IERC1155ApprovalByAmount is IERC1155 { - - /** - * @notice Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to - * `id` and with an amount: `amount`. - */ - event ApprovalByAmount(address indexed account, address indexed operator, uint256 id, uint256 amount); - - /** - * @notice Grants permission to `operator` to transfer the caller's tokens, according to `id`, and an amount: `amount`. - * Emits an {ApprovalByAmount} event. - * - * Requirements: - * - `operator` cannot be the caller. - */ - function approve(address operator, uint256 id, uint256 amount) external; - - /** - * @notice Returns the amount allocated to `operator` approved to transfer `account`'s tokens, according to `id`. - */ - function allowance(address account, address operator, uint256 id) external view returns (uint256); - -} - -/** - * @dev Extension of {ERC1155} that allows you to approve your tokens by amount and id. - */ -abstract contract ERC1155ApprovalByAmount is ERC1155, IERC1155ApprovalByAmount { - - // Mapping from account to operator approvals by id and amount. - mapping(address => mapping(address => mapping(uint256 => uint256))) internal _allowances; - - /** - * @dev See {IERC1155ApprovalByAmount} - */ - function approve(address operator, uint256 id, uint256 amount) public virtual { - _approve(msg.sender, operator, id, amount); - } - - /** - * @dev See {IERC1155ApprovalByAmount} - */ - function allowance(address account, address operator, uint256 id) public view virtual returns (uint256) { - return _allowances[account][operator][id]; - } - - /** - * @dev safeTransferFrom implementation for using ApprovalByAmount extension - */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public override(IERC1155, ERC1155) { - require( - from == msg.sender || isApprovedForAll(from, msg.sender) || allowance(from, msg.sender, id) >= amount, - "ERC1155: caller is not owner nor approved nor approved for amount" - ); - unchecked { - _allowances[from][msg.sender][id] -= amount; - } - _safeTransferFrom(from, to, id, amount, data); - } - - /** - * @dev safeBatchTransferFrom implementation for using ApprovalByAmount extension - */ - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public virtual override(IERC1155, ERC1155) { - require( - from == msg.sender || isApprovedForAll(from, msg.sender) || _checkApprovalForBatch(from, msg.sender, ids, amounts), - "ERC1155: transfer caller is not owner nor approved nor approved for some amount" - ); - _safeBatchTransferFrom(from, to, ids, amounts, data); - } - - /** - * @dev Checks if all ids and amounts are permissioned for `to`. - * - * Requirements: - * - `ids` and `amounts` length should be equal. - */ - function _checkApprovalForBatch( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts - ) internal virtual returns (bool) { - uint256 idsLength = ids.length; - uint256 amountsLength = amounts.length; - - require(idsLength == amountsLength, "ERC1155ApprovalByAmount: ids and amounts length mismatch"); - for (uint256 i = 0; i < idsLength;) { - require(allowance(from, to, ids[i]) >= amounts[i], "ERC1155ApprovalByAmount: operator is not approved for that id or amount"); - unchecked { - _allowances[from][to][ids[i]] -= amounts[i]; - ++i; - } - } - return true; - } - - /** - * @dev Approve `operator` to operate on all of `owner` tokens by id and amount. - * Emits a {ApprovalByAmount} event. - */ - function _approve( - address owner, - address operator, - uint256 id, - uint256 amount - ) internal virtual { - require(owner != operator, "ERC1155ApprovalByAmount: setting approval status for self"); - _allowances[owner][operator][id] = amount; - emit ApprovalByAmount(owner, operator, id, amount); - } -} - -contract ExampleToken is ERC1155ApprovalByAmount { - constructor() ERC1155("") {} - - function mint(address account, uint256 id, uint256 amount, bytes memory data) public { - _mint(account, id, amount, data); - } - - function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public { - _mintBatch(to, ids, amounts, data); - } -} diff --git a/assets/eip-5218/contracts/README.md b/assets/eip-5218/contracts/README.md deleted file mode 100644 index 705973c..0000000 --- a/assets/eip-5218/contracts/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# EIP-5218 Reference Implementations - -This is the source code for a reference implementation of EIP-5218. - -## Build and Test - -The repo expects a [Foundry](https://github.com/foundry-rs/foundry/tree/master/forge) build system, optionally using visual studio code for editing. You can run the test suite with: - -```bash -forge test -vvvvv -``` - diff --git a/assets/eip-5218/contracts/foundry.toml b/assets/eip-5218/contracts/foundry.toml deleted file mode 100644 index d7dd144..0000000 --- a/assets/eip-5218/contracts/foundry.toml +++ /dev/null @@ -1,6 +0,0 @@ -[default] -src = 'src' -out = 'out' -libs = ['lib'] - -# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/assets/eip-5218/contracts/remappings.txt b/assets/eip-5218/contracts/remappings.txt deleted file mode 100644 index 1e11e4d..0000000 --- a/assets/eip-5218/contracts/remappings.txt +++ /dev/null @@ -1,3 +0,0 @@ -forge-std/=lib/forge-std/src/ -ds-test/=lib/forge-std/lib/ds-test/src/ -@openzeppelin/=lib/openzeppelin-contracts/ \ No newline at end of file diff --git a/assets/eip-5218/contracts/src/IERC5218.sol b/assets/eip-5218/contracts/src/IERC5218.sol deleted file mode 100644 index 334f235..0000000 --- a/assets/eip-5218/contracts/src/IERC5218.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/// @title EIP-5218: NFT Rights Management -interface IERC5218 is IERC721 { - - /// @dev This emits when a new license is created by any mechanism. - event CreateLicense(uint256 _licenseId, uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string _uri, address _revoker); - - /// @dev This emits when a license is revoked. Note that under some - /// license terms, the sublicenses may be `implicitly` revoked following the - /// revocation of some ancestral license. In that case, your smart contract - /// may only emit this event once for the ancestral license, and the revocation - /// of all its sublicenses can be implied without consuming additional gas. - event RevokeLicense(uint256 _licenseId); - - /// @dev This emits when the a license is transferred to a new holder. The - /// root license of an NFT should be transferred with the NFT in an ERC721 - /// `transfer` function call. - event TransferLicense(uint256 _licenseId, address _licenseHolder); - - /// @notice Check if a license is active. - /// @dev A non-existing or revoked license is inactive and this function must - /// return `false` upon it. Under some license terms, a license may become - /// inactive because some ancestral license has been revoked. In that case, - /// this function should return `false`. - /// @param _licenseId The identifier for the queried license - /// @return Whether the queried license is active - function isLicenseActive(uint256 _licenseId) external view returns (bool); - - /// @notice Retrieve the token identifier a license was issued upon. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The token identifier the queried license was issued upon - function getLicenseTokenId(uint256 _licenseId) external view returns (uint256); - - /// @notice Retrieve the parent license identifier of a license. - /// @dev Throws unless the license is active. If a license doesn't have a - /// parent license, return a special identifier not referring to any license - /// (such as 0). - /// @param _licenseId The identifier for the queried license - /// @return The parent license identifier of the queried license - function getParentLicenseId(uint256 _licenseId) external view returns (uint256); - - /// @notice Retrieve the holder of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The holder address of the queried license - function getLicenseHolder(uint256 _licenseId) external view returns (address); - - /// @notice Retrieve the URI of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The URI of the queried license - function getLicenseURI(uint256 _licenseId) external view returns (string memory); - - /// @notice Retrieve the revoker address of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The revoker address of the queried license - function getLicenseRevoker(uint256 _licenseId) external view returns (address); - - /// @notice Retrieve the root license identifier of an NFT. - /// @dev Throws unless the queried NFT exists. If the NFT doesn't have a root - /// license tethered to it, return a special identifier not referring to any - /// license (such as 0). - /// @param _tokenId The identifier for the queried NFT - /// @return The root license identifier of the queried NFT - function getLicenseIdByTokenId(uint256 _tokenId) external view returns (uint256); - - /// @notice Create a new license. - /// @dev Throws unless the NFT `_tokenId` exists. Throws unless the parent - /// license `_parentLicenseId` is active, or `_parentLicenseId` is a special - /// identifier not referring to any license (such as 0) and the NFT - /// `_tokenId` doesn't have a root license tethered to it. Throws unless the - /// message sender is eligible to create the license, i.e., either the - /// license to be created is a root license and `msg.sender` is the NFT owner, - /// or the license to be created is a sublicense and `msg.sender` is the holder - /// of the parent license. - /// @param _tokenId The identifier for the NFT the license is issued upon - /// @param _parentLicenseId The identifier for the parent license - /// @param _licenseHolder The address of the license holder - /// @param _uri The URI of the license terms - /// @param _revoker The revoker address - /// @return The identifier of the created license - function createLicense(uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string memory _uri, address _revoker) external returns (uint256); - - /// @notice Revoke a license. - /// @dev Throws unless the license is active and the message sender is the - /// eligible revoker. This function should be used for revoking both root - /// licenses and sublicenses. Note that if a root license is revoked, the - /// NFT should be transferred back to its creator. - /// @param _licenseId The identifier for the queried license - function revokeLicense(uint256 _licenseId) external; - - /// @notice Transfer a sublicense. - /// @dev Throws unless the sublicense is active and `msg.sender` is the license - /// holder. Note that the root license of an NFT should be tethered to and - /// transferred with the NFT. Whenever an NFT is transferred by calling the - /// ERC721 `transfer` function, the holder of the root license should be - /// changed to the new NFT owner. - /// @param _licenseId The identifier for the queried license - /// @param _licenseHolder The new license holder - function transferSublicense(uint256 _licenseId, address _licenseHolder) external; -} - diff --git a/assets/eip-5218/contracts/src/RightsManagement.sol b/assets/eip-5218/contracts/src/RightsManagement.sol deleted file mode 100644 index 179b1cd..0000000 --- a/assets/eip-5218/contracts/src/RightsManagement.sol +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC5218.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; - - -contract RightsManagement is IERC5218, ERC721URIStorage, Ownable { - struct License { - bool active; // whether the current license is active - uint256 tokenId; - uint256 parentLicenseId; - address licenseHolder; - string uri; - address revoker; - } - mapping(uint256 => License) private _licenses; - mapping(uint256 => uint256) private _licenseIds; - - using Counters for Counters.Counter; - Counters.Counter private _tokenCounter; - Counters.Counter private _licenseCounter; - - constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) { - return - interfaceId == type(IERC5218).interfaceId || - super.supportsInterface(interfaceId); - } - - function isLicenseActive(uint256 licenseId) public view virtual override(IERC5218) returns (bool) { - if (licenseId == 0) return false; - while (licenseId != 0) { - if (!_licenses[licenseId].active) return false; - licenseId = _licenses[licenseId].parentLicenseId; - } - return true; - } - - modifier isActiveLicense(uint256 licenseId) { - require(isLicenseActive(licenseId), "The queried license is not active"); - _; - } - - function getLicenseTokenId(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (uint256) { - return _licenses[licenseId].tokenId; - } - - function getParentLicenseId(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (uint256) { - return _licenses[licenseId].parentLicenseId; - } - - function getLicenseHolder(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (address) { - return _licenses[licenseId].licenseHolder; - } - - function getLicenseURI(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (string memory) { - return _licenses[licenseId].uri; - } - - function getLicenseRevoker(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (address) { - return _licenses[licenseId].revoker; - } - - function getLicenseIdByTokenId(uint256 tokenId) public view virtual override(IERC5218) returns (uint256) { - require (_exists(tokenId), "The token doesn't exist"); - return _licenseIds[tokenId]; - } - - function safeMint( - address recipient, - string memory tokenURI, - string memory licenseURI, - address licenseRevoker - ) - public virtual onlyOwner - returns (uint256) - { - return safeMint(recipient, tokenURI, licenseURI, licenseRevoker, ""); - } - - function safeMint( - address recipient, - string memory tokenURI, - string memory licenseURI, - address licenseRevoker, - bytes memory _data - ) - public virtual onlyOwner - returns (uint256) - { - _tokenCounter.increment(); - uint256 newItemId = _tokenCounter.current(); - - _safeMint(recipient, newItemId, _data); - _setTokenURI(newItemId, tokenURI); - _createLicense(newItemId, 0, recipient, licenseURI, licenseRevoker); - - return newItemId; - } - - function safeIssue( - address recipient, - uint256 tokenId, - string memory licenseURI, - address licenseRevoker - ) - public virtual - returns (uint256) - { - return safeIssue(recipient, tokenId, licenseURI, licenseRevoker, ""); - } - - function safeIssue( - address recipient, - uint256 tokenId, - string memory licenseURI, - address licenseRevoker, - bytes memory data - ) - public virtual - returns (uint256) - { - require(_licenseIds[tokenId] == 0, "The token has an active license"); - require(ownerOf(tokenId) == owner(), "The creator doesn't own the NFT"); - - uint256 licenseId = createLicense(tokenId, 0, owner(), licenseURI, licenseRevoker); - safeTransferFrom(owner(), recipient, tokenId, data); - - return licenseId; - } - - function createLicense( - uint256 tokenId, - uint256 parentLicenseId, - address licenseHolder, - string memory uri, - address revoker - ) - public virtual override(IERC5218) - returns (uint256) - { - require(_exists(tokenId), "The NFT doesn't exists"); - require(parentLicenseId == 0 || isLicenseActive(parentLicenseId), "The parent license is not active"); - require(parentLicenseId != 0 || getLicenseIdByTokenId(tokenId) == 0, "The NFT already has a root license"); - require( - (parentLicenseId == 0 && msg.sender == owner()) || - (parentLicenseId != 0 && msg.sender == _licenses[parentLicenseId].licenseHolder), - "Sender is not eligible to grant a new license" - ); - - return _createLicense(tokenId, parentLicenseId, licenseHolder, uri, revoker); - } - - function revokeLicense(uint256 licenseId) public virtual override(IERC5218) { - require(isLicenseActive(licenseId), "The license is not active"); - require(msg.sender == _licenses[licenseId].revoker, "The msg sender is not an eligible revoker"); - - if (_licenses[licenseId].parentLicenseId == 0) { - _transfer(ownerOf(_licenses[licenseId].tokenId), owner(), _licenses[licenseId].tokenId); - } - - _revokeLicense(licenseId); - } - - function transferSublicense(uint256 licenseId, address licenseHolder) public virtual override(IERC5218) { - require(isLicenseActive(licenseId), "The license is not active"); - require(_licenses[licenseId].parentLicenseId != 0, "The license is a root license"); - require(msg.sender == _licenses[licenseId].licenseHolder, "The msg sender is not the license holder"); - - _updateLicenseHolder(licenseId, licenseHolder); - } - - function _transfer(address from, address to, uint256 tokenId) internal virtual override(ERC721) { - require(_licenseIds[tokenId] != 0 && isLicenseActive(_licenseIds[tokenId]), "The token has no active license tethered to it"); - require(_licenses[_licenseIds[tokenId]].licenseHolder == ownerOf(tokenId), "The license holder and the NFT owner are inconsistent"); - - super._transfer(from, to, tokenId); - _updateLicenseHolder(_licenseIds[tokenId], to); - } - - function _updateLicenseHolder(uint256 licenseId, address licenseHolder) internal virtual { - _licenses[licenseId].licenseHolder = licenseHolder; - emit TransferLicense(licenseId, licenseHolder); - } - - function _createLicense( - uint256 tokenId, - uint256 parentLicenseId, - address licenseHolder, - string memory uri, - address revoker - ) - internal virtual - returns (uint256) - { - _licenseCounter.increment(); - uint256 licenseId = _licenseCounter.current(); - - _licenses[licenseId].active = true; - _licenses[licenseId].tokenId = tokenId; - _licenses[licenseId].parentLicenseId = parentLicenseId; // tyler: it seems like a security problem that children are able to overwrite their parents - _licenses[licenseId].licenseHolder = licenseHolder; - _licenses[licenseId].uri = uri; - _licenses[licenseId].revoker = revoker; - - if (parentLicenseId == 0) { - _licenseIds[tokenId] = licenseId; - } - - emit CreateLicense(licenseId, tokenId, parentLicenseId, licenseHolder, uri, revoker); - return licenseId; - } - - function _revokeLicense(uint256 licenseId) internal virtual { - if (_licenses[licenseId].parentLicenseId == 0) { - _licenseIds[_licenses[licenseId].tokenId] = 0; - } - - delete _licenses[licenseId]; - - emit RevokeLicense(licenseId); - } -} - - - diff --git a/assets/eip-5218/contracts/test/Contract.t.sol b/assets/eip-5218/contracts/test/Contract.t.sol deleted file mode 100644 index 84a2e6c..0000000 --- a/assets/eip-5218/contracts/test/Contract.t.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../src/RightsManagement.sol"; - -contract ContractTest is Test { - - event CreateLicense( - uint256 licenseId, - uint256 tokenId, - uint256 parentLicenseId, - address licenseHolder, - string uri, - address revoker - ); - event RevokeLicense(uint256 licenseId); - event TransferLicense(uint256 licenseId, address licenseHolder); - - //Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - RightsManagement rm; - - address add1 = address(0xadd1); - address add2 = address(0xadd2); - address add3 = address(0xadd3); - string tokenURI = "tokenURI"; - string licenseURI = "licenseURI"; - string sublicenseURI = "sublicenseURI"; - - function setUp() public { - vm.deal(add1, 12 ether); - vm.deal(add2, 12 ether); - vm.deal(add3, 12 ether); - - rm = new RightsManagement("MyNFT", "NFT"); - } - - function testMint() public { - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(1, 1, 0, add1, licenseURI, add3); // the expected log you expect to see emitted - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - address tokenOwner = rm.ownerOf(tokenId); - assertEq(add1, tokenOwner, "tokenOwner should match"); - uint256 licenseId = rm.getLicenseIdByTokenId(tokenId); - assertEq(rm.isLicenseActive(0), false, "License 0 should be inactive"); - assertEq(rm.isLicenseActive(licenseId), true, "License should be active"); - assertEq(rm.getLicenseURI(licenseId), licenseURI, "License should match"); - assertEq(rm.getLicenseHolder(licenseId), add1, "LicenseHolder should match"); - assertEq(rm.getLicenseTokenId(licenseId), 1, "TokenId should match"); - assertEq(rm.getParentLicenseId(licenseId), 0, "Parent License Id should match"); - assertEq(rm.getLicenseRevoker(licenseId), add3, "License revoker should match"); - } - - function testCreateLicense() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - uint parentLicenseId = rm.getLicenseIdByTokenId(tokenId); - - vm.startPrank(add1); - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 1, add2, sublicenseURI, add3); // the expected log you expect to see emitted - uint licenseId = rm.createLicense(tokenId, parentLicenseId, add2, sublicenseURI, add3); - vm.stopPrank(); - - vm.expectRevert("Sender is not eligible to grant a new license"); - licenseId = rm.createLicense(tokenId, parentLicenseId, add2, sublicenseURI, add3); - } - - function testRevokeLicense() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - uint parentLicenseId = rm.getLicenseIdByTokenId(tokenId); - - vm.startPrank(add1); - uint licenseId = rm.createLicense(tokenId, parentLicenseId, add2, sublicenseURI, add3); - vm.stopPrank(); - - vm.startPrank(add2); - licenseId = rm.createLicense(tokenId, licenseId, add3, sublicenseURI, add3); - vm.stopPrank(); - - vm.startPrank(add3); - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit RevokeLicense(2); // the expected log you expect to see emitted - rm.revokeLicense(2); - vm.stopPrank(); - - vm.startPrank(add3); - vm.expectRevert("The license is not active"); - rm.revokeLicense(3); - vm.stopPrank(); - - - vm.startPrank(add1); - vm.expectRevert("The msg sender is not an eligible revoker"); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - assertEq(rm.ownerOf(1), address(this), "The token should be returned to creator after revoking its license"); - - assertEq(rm.getLicenseIdByTokenId(1), 0, "The token should not have an active license"); - } - - function testTransfer() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.expectRevert("The token has no active license tethered to it"); - rm.safeTransferFrom(address(this), add1, 1); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 0, address(this), licenseURI, add3); // the expected log you expect to see emitted - rm.createLicense(tokenId, 0, address(this), licenseURI, add3); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit TransferLicense(2, add2); // the expected log you expect to see emitted - rm.safeTransferFrom(address(this), add2, 1); - assertEq(rm.getLicenseIdByTokenId(1), 2, "License Id linked to tokenId"); - assertEq(rm.getLicenseHolder(2), add2, "License holder updated"); - } - - function testIssue() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 0, address(this), licenseURI, add3); // the expected log you expect to see emitted - emit TransferLicense(2, add2); // the expected log you expect to see emitted - rm.safeIssue(add2, tokenId, licenseURI, add3); - assertEq(rm.getLicenseIdByTokenId(1), 2, "License Id linked to tokenId"); - assertEq(rm.getLicenseHolder(2), add2, "License holder updated"); - } - - function testTransferSublicense() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.expectRevert("The license is not active"); - rm.transferSublicense(1, add2); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 0, add1, licenseURI, add3); // the expected log you expect to see emitted - rm.createLicense(tokenId, 0, add1, licenseURI, add3); - - vm.expectRevert("The license is a root license"); - rm.transferSublicense(2, add2); - - vm.startPrank(add1); - rm.createLicense(tokenId, 2, add2, licenseURI, add3); - vm.stopPrank(); - - vm.expectRevert("The msg sender is not the license holder"); - rm.transferSublicense(3, add1); - - vm.startPrank(add2); - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit TransferLicense(3, add1); // the expected log you expect to see emitted - rm.transferSublicense(3, add1); - vm.stopPrank(); - } -} diff --git a/assets/eip-5218/ic3license/ic3license.pdf b/assets/eip-5218/ic3license/ic3license.pdf deleted file mode 100644 index 351a73f..0000000 Binary files a/assets/eip-5218/ic3license/ic3license.pdf and /dev/null differ diff --git a/assets/eip-5218/ic3license/ic3license.tex b/assets/eip-5218/ic3license/ic3license.tex deleted file mode 100644 index 2c665dd..0000000 --- a/assets/eip-5218/ic3license/ic3license.tex +++ /dev/null @@ -1,355 +0,0 @@ -\documentclass{article} - -\usepackage[hidelinks]{hyperref} -\usepackage{libertine} -\usepackage{authblk} - -\title{The \iccclicense\\} -\author{The Institute for Cryptocurrencies and Contracts (IC3) \and The Coalition of Automated Legal Applications (COALA)} -\date{Version 1.0\\\today} - -\usepackage{xspace} - -\newcommand{\eiplicense}{EIP-5218\xspace} -\newcommand{\iccclicense}{Token-Bound NFT License\xspace} - -\newcommand{\keyword}[1]{\textbf{#1}\xspace} - -\newcommand{\publiclicense}{\keyword{Public-License}} -\newcommand{\nopubliclicense}{\keyword{No-Public-License}} - -\newcommand{\commercial}{\keyword{Commercial}} -\newcommand{\noncommercial}{\keyword{Non-Commercial}} - -\newcommand{\noderivative}{\keyword{No-Derivatives}} -\newcommand{\derivative}{\keyword{Derivatives}} -\newcommand{\derivativetracking}{\keyword{Derivatives-NFT}} -\newcommand{\sharealike}{\keyword{Derivatives-NFT-Share-Alike}} - -\newcommand{\ledger}{\keyword{Ledger-Authoritative}} -\newcommand{\legal}{\keyword{Legal-Authoritative}} - -\newcommand{\personal}{\keyword{Personal}} -\newcommand{\conditional}{\keyword{Conditional}} - - -\usepackage{semantic-markup} - -\newcommand{\sect}[1]{\vspace{12pt}\noindent{\strong{#1}}} -\newcommand{\subsect}[1]{\vspace{12pt}\noindent{\em{#1}}} - - -\usepackage{xcolor} - -\definecolor{light-gray}{gray}{0.9} - -\renewcommand{\code}[1]{\colorbox{light-gray}{\texttt{#1}}} - -\newcommand{\iflicenseoption}[2]{[\colorbox{light-gray}{If #1:} #2]} -\newcommand{\ifnotlicenseoption}[2]{[\colorbox{light-gray}{Unless #1:} #2]} - -\begin{document} - -\maketitle - -\tableofcontents - -\section{Introduction} - -The \iccclicense is a copyright license specifically designed for use with NFTs. It links a creative work to an NFT so that when the NFT is transferred, so is the license. It is intended to be compatible with the NFT licensing standard defined in \eiplicense. - -The \iccclicense has four optional features that a licensor can use or not when choosing a license: -\begin{itemize} -\item The license can be \commercial (C) or \noncommercial (NC). A \commercial license allows the user to make money from merchandise and other uses of the work; a \noncommercial license does not. -\item The license can be \derivative (D), \derivativetracking (DT), \sharealike (DTSA), or \noderivative (ND). A \derivative license allows the user to make derivative works and adaptations (like remixes and videos) without restriction. A \derivativetracking license allows derivative works but requires them to be registered and tracked as NFTs themselves. A \sharealike license is like \derivativetracking but also requires that the derivative works be relicensed under the same license. And a \noderivative license prohibits derivative works entirely. (These options are ordered from least restrictive to most restrictive.) -\item The license can be \publiclicense (PL) or \nopubliclicense (NPL). A \publiclicense license, in addition to the specific rights granted to the user, grants to the public a broad copyright license to reproduce the work (but not to make derivative works). A \nopubliclicense license grants only the specific rights to the user. -\item The license can be \ledger (Ledger) or \legal (Legal). A \ledger license means that the current state of the blockchain ledger is always authoritative for who owns the NFT and has rights under the license, even if the NFT is stolen or transferred by mistake. A \legal license gives the courts flexibility to correct ownership of the license in clear cases of theft and fraud. -\end{itemize} -All four options are independent, so that they can be set for a choice of thirty-two total licenses, e.g. NC-D-NPL-Ledger means the license version that allows only Non-Commercial uses, allows Derivative works, includes No Public License, and makes the Ledger authoritative for the state of the license. Once an \iccclicense has been used on an NFT, that license stays with it. The licensor should not expect to be able to choose a different license after the initial release. - -The simplest combination of license options is C-D-NPL-Ledger, which provides a straightforward license that gives the current owner of the NFT (as defined by the blockchain) full rights over the associated work. This is the default \iccclicense; the other license options can be thought of as variations on it. - -The rest of this document describes the design choices of the \iccclicense. Appendix \ref{sec:human} provides a short human-readable summary of the license terms. Appendix \ref{sec:text} contains the actual text of the license family. - - -\section{Design Goals} - - -The \iccclicense has been specifically designed to work with blockchain-based NFTs, e.g. with smart contracts that implement the standard defined in \href{https://eips.ethereum.org/EIPS/eip-721}{EIP-721}. But it is not restricted to blockchain-based NFTs. The license uses the technology-neutral term ``Ledger'' to refer to the system that records information about NFTs and their owners. This could be a present-day blockchain, or another chain or system that could be created in the future. The license can be used with any underlying technology as long as it: -\begin{enumerate} -\item Supports NFTs based on persistent unique identifiers, -\item Associates those NFTs with specific owners, -\item Allows the owner of an NFT to control it with a cryptographic key, and -\item Allows an NFT to be linked to a creative work. -\end{enumerate} -This system can be widely distributed (like a public blockchain), moderately distributed (like a private blockchain), or fully centralized (like a database with a single administrator). Use of the \iccclicense for other kinds of licensing is strongly discouraged. - -The \iccclicense has also been specifically designed to work with NFTs that implement the interface defined in \eiplicense. This is a general smart-contract interface that provides modular support for common features in copyright licensing, including transfer, sublicensing, and revocation. The \iccclicense can also be used with NFTs that implement the generic NFT interface defined in EIP-721, or with other NFTs, but the \eiplicense interface is recommended to reduce ambiguity about when there has been a transfer, sublicense, or revocation. - - -\section{Taking Effect} - -The \iccclicense is structured as a license, rather than a contract. The licensor unilaterally grants a copyright license to the current owner of the NFT. The owner does not need to do anything to accept the license; they receive it automatically. If the owner transfers the NFT to someone else, the license transfers with it. The old owner is no longer licensed; the new owner receives a license on exactly the same terms. This too is automatic, the new owner does not need to do anything to accept the license. - -Standard techniques used by lawyers to create binding terms of use for websites do not work for NFTs on blockchains. Consider the clickthrough agreements used by websites: when you create an account, you must check a box indicating that you agree to the website's terms of use. This works because you have taken an action (checking the box) that is clearly and specifically linked to the legal terms and nothing else. There is no question about whether you meant to agree when you checked the box, because there is no other reason to check the box. - -But a blockchain does not have a checkbox or even a website. Someone who receives an EIP-721 NFT need not have done anything at all. They may never have visited the NFT sponsor's website, or even know that there is a website. An NFT could migrate from one resale platform to another; someone who has agreed to one platform's terms might not have agreed to the other's. This means that any promises by the \emph{licensee} in an NFT license contract are illusory. The licensor has no guarantee that a downstream NFT owner has actually agreed to the license terms. Thus, the \iccclicense does not give the licensee any duties, so there is no need for them to agree to anything. - -On the other hand, the \iccclicense does attempt to ensure that the \emph{licensor} has clearly indicated their intent to be bound by the license terms. It does so by saying that the license becomes effective when the licensor ``Invokes'' it through a ``Licensing Process,'' i.e., uses a technical process to connect an NFT with the \iccclicense. This definition includes (but is not limited to) the \eiplicense interface. - -The essential idea of \eiplicense is to make it explicit when a licensor intends to create a copyright license. This signal is clearest when the licensor takes an act that (a) says that it creates a license, and (b) does not do anything else. Signing a licensing contract on paper meets this test, because the paper says that it creates a license and the only reason to sign it is to create the license. \eiplicense is intended to be a smart-contract version of that piece of paper. - -The \iccclicense can be used with any smart contract or other technical process that meets the definition of a Licensing Process. It is not intended to be used on a website or in other off-chain settings where on-chain transactions do not link out to the license. It is not recommended to use the \iccclicense as part of a larger multi-step architecture unless there is some individual step in the system that meets the definition of a Licensing Process in which the licensor clearly indicates their intent to create a license. - - -\section{License Terms} - -We have tried in \iccclicense to capture the most common and important use cases in light of the NFT community's responses to previous licenses. But our goal has been simplicity rather than perfection. We hope and expect that over time, others will build on and remix the ideas in the license to terms for more advanced use cases. - -The \iccclicense applies to ``Licensed Material'' that is ``Linked'' to an NFT. The definition of Licensed Material is deliberately broad: it can include highly creative works like images or videos, but it also includes ``other material'' to include datasets, software, or other works that are not primarily artistic. The definition of Linked is also broad: it can be by hyperlink, by description, by IPFS CID, by hash value, by an on-chain reference, etc. What is important is that the NFT must be connected to specific licensed material in a way that cannot change over time. We do not attempt to solve the (very difficult) problems of creating a technical standard or a license for an NFT whose contents can change over time. - -The core license grant in the \iccclicense is that the NFT owner can ``Use'' the licensed material, i.e. exercise all of the usual rights under copyright to make copies of the work and share them with the public. This is an unlimited grant: it covers all media, digital and analog, on-chain and off-chain. - -The license grant is non-exclusive. It is not currently possible to guarantee that only one NFT has been made of a work, or that there are no other off-chain uses of the work. - -Drawing on the success of the Creative Commons license suite, the \iccclicense can be customized in four ways: -\begin{enumerate} -\item To be either \commercial or \noncommercial. -\item To allow \derivative works, to allow them but require that they be \derivativetracking on chain, to allow them but require that they be relicensed under \sharealike terms, or to specify that \noderivative works are allowed at all. -\item To grant a \publiclicense to members of the public besides the NFT owner, or to grant \nopubliclicense. -\item To track ownership of the NFT entirely on-chain (\ledger) or to follow the legal system's rules on ownership (\legal). (This option is discussed in section~\ref{sec:trans}.) -\end{enumerate} -These four options can be set independently, for a total of 32 theoertically possible license variations. However, it does not generally make sense to use both \noderivative and \publiclicense options, because then the NFT owner doesn't receive any useful rights beyond what everyone receives under the public license grant. - -Regardless of which license options are used, all versions of the \iccclicense include two specific uses that are always allowed. First, the material can be used to sell the NFT, e.g. on an online NFT marketplace listing. This is an essential part of truth in advertising; someone considering buying the NFT typically needs to be able to see what creative work they will actually be getting. A similar clause is present in many other NFT licenses, although we have attempted to generalize it so that it is less tied to the specifics of how any particular NFT marketplace works. Second, the \iccclicense generally allows free use of the material to identify the NFT owner publicly, e.g. in a social-media profile. This too is widespread in the NFT community and widely allowed by other NFT licenses. - - -\subsection{Commercial Uses} - -If the license is \noncommercial, it excludes any uses directed to ``commercial advantage or monetary compensation,'' which includes any cases in which people are required to pay to get a copy of the work. The definition of the \noncommercial license option specifically \emph{allows} the sale of the NFT itself. The resale of NFTs, like the resale of unique works of fine art, is a recognized and important use case. What the \noncommercial license option prohibits is the commercialization of the work by making and selling \emph{other} copies of the work, such as selling posters of an image attached to an NFT, or creating a video series based on a character depicted in a work associated with an NFT. - -Whether the license is \commercial or \noncommercial, no royalties are required. Under the \commercial license option, the NFT owner is allowed to keep all of their revenues from commercializing the work. Under the \noncommercial license option, such commercialization is not allowed at all, and constitutes a breach of the license allowing the licensor to sue for infringement. Royalties pose complicated computation and drafting problems, which we have not attempted to solve. If a platform charges a fee for an NFT listing or sale, this is neither required nor prohibited by the \iccclicense; it is an issue between the NFT owner and the platform, not between the NFT owner and the licensor. - -Similarly, if the NFT owner sells the NFT, this is not an event that entitles the NFT owner to any royalties under this license. This does not prevent the \iccclicense from being used with a smart contract that requires royalty payments. We merely have not attempted to make payment of royalties part of the copyright license. For example, a smart contract could tie invocation of NFT transfer functions to payment of required royalties. (But see \href{https://eips.ethereum.org/EIPS/eip-2981}{EIP-2981} (NFT Royalty Standard) for discussion of the reasons why such requirements may not be effective in practice.) - -\subsection{Derivative Works} - -If the licensor chooses to allow derivative works with \derivative, \derivativetracking, or \sharealike, the license grant also allows the NFT owner to use ``Adapted Material,'' which uses language from the Creative Commons license suite to define what counts as a derivative work. To reflect the customs of the NFT community, we added language to make clear that simply reproducing the work in a different medium -- e.g., printing T-shirts of a JPEG -- doesn't count as making a derivative work. Only new projects -- such as modifying artwork, remixing a song, or making a TV series based on a character -- are derivative works. - -The options to enable derivative works are cumulative: \derivative enables the creation of derivative works in all circumstances, \derivativetracking includes the derivative conditions, and introduces additional requirements concerning the technical properties of the NFT associated with the derivative work; and \sharealike includes all of the above conditions, and introduces an additional requirement concerning the legal licensing terms of the derivative work. - -When the license requires \derivativetracking, the grant of permission to make derivative works is conditional on creating an NFT for the derivative work that (1) subsists on the same ledger as the original NFT; (2) has materially the same properties and functionality as the original NFT; (3) causes the derivative NFT to reference the original NFT in such a way as to ensure that for each NFT, it is technically straightforward to identify all of its derivative NFTs. This option implements a \emph{technical} compatibility condition, one that aims to keep derivative works on the \emph{same technical standard} and on the \emph{same ledger} as the original NFT. - -When the license requires Share-Alike, the requirement of using the same technical mechanism (as prescribed by \derivativetracking) is supplemented with the further requirement that the derivative work be licensed using the same licensing terms. This is a \emph{legal compatibility} condition; it closely corresponds to the Share-Alike license option in the Creative Commons license suite. Note that \sharealike implies \derivativetracking because in the context of NFTs, the point of a Share-Alike license could be defeated by tying the license to a derivative-work NFT whose technical implementation departed too significantly from the implementation of the underlying NFT. This means that not only must the NFT associated with the derivative work implement the same technical standards, but it must also adopt the same properties and parameters as the original-work NFT in order to comply with the terms of the legal license. - -\subsection{Public Licenses} - -One common use case for NFTs is to reserve a few privileges for the NFT owner while giving the public a license to use the creative work associated with the NFT. This could be accomplished through dual licensing, in which the creator explicitly uses one license for the NFT owner and another for the public. But this approach is unsatisfactory; dual licensing gives up some of the convenience and clarity of having all of the relevant license terms clearly located in one place. Nor is it appropriate to include a public license in every version of the NFT license. The public-license model for NFTs is not universal; many NFTs give rights in the creative work only to the NFT owner. - -Thus, the \iccclicense includes a public license as a license option. When \publiclicense is selected, the license grants described above, which give specific broad rights to the NFT owner, are accompanied by a public license grant (specifically, a Creative Commons Attribution-NoDerivatives license) that gives the public the right to use the work, but not to make derivative works. - - - -\section{Transfer} -\label{sec:trans} - -The \iccclicense tries to deal sensibly with the many complications that can arise due to the unrestricted transferability of NFTs. Most existing NFT licenses have not taken this possibility seriously, even though it is arguably the defining characteristic of NFTs and the one that makes them most appealing to users. - -The first major issue is that NFTs can be, and frequently are, stolen. A hacker or phisher gains access to an NFT owner's private key and uses it to transfer the NFT to themselves, frequently turning around and immediately reselling it. Under these circumstances, should the \emph{copyright} license go to the new NFT owner or stay with the previous NFT owner? -\begin{itemize} -\item Many blockchain technologists believe that the first answer is obviously correct. The license follows whoever is the owner of the NFT according to the blockchain. The previous NFT owner's license terminates, and the new NFT owner receives a license. This approach makes the blockchain reliable, but it also makes it difficult for NFT owners to commercialize derivative works of their NFTs unless they take extreme security measures. -\item Many lawyers believe that the second answer is obviously correct. The license stays with the true owner of the NFT according to property law. The previous NFT owner retains their copyright license, and the thief takes nothing. This approach protects NFT owners against theft, fraud, and duress, and makes the NFT copyright licensing more consistent with the rest of the legal system, but it makes the blockchain non-authoritative and can require additional investigation on the part of buyers and licensees. -\end{itemize} - -This is an irreconcilable difference of views about how ledgers ought to operate, and a copyright license cannot resolve this deeper split of opinion within the NFT community. Instead, the \iccclicense provides both options, and the licensor can choose which one to use by choosing an appropriate variant of the license. The first option is called \ledger because it makes the ledger authoritative as to ownership. The second option is called called \legal because it makes the legal system authoritative as to ownership. - -To keep the distinction clear, the license uses the term ``Controller'' for the person who has control of an NFT via a private key, and ``Owner'' for the person who is legally entitled to the license. The \ledger option is implemented by defining the Owner to be the Controller (so the two concepts are the same), and the \legal option by defining the Owner to be the ``person or entity who is legally entitled to be its Controller.'' (In theory, it would be possible for a license to fine-tune the circumstances under which it does and does not transfer by stating its own rules, but at the cost of decreased compatibility with both the blockchain and with the legal system. The \iccclicense does not attempt this task.) - -The licensor may optionally provide a ``Grace Period'' as part of the smart contract for the NFT. If present, it allows a NFT owner to continue using the work (but not to create new derivatives of it) for a period of time following the transfer of the NFT. The Grace Period is defined in terms of a ``Grace Period Process,'' e.g. a smart-contract method that indicates whether the grace period has expired or not. In our view, this is the most general solution to the question of how long a grace period should be, if any. The licensor is not locked in to any particular choice, and putting this function in the smart-contract logic avoids any difficulties translating human-readable terms like ``two days'' into computation-friendly form. - -\section{Revocation} - -The \iccclicense can be revoked, but it does not define the conditions of revocation. This may seem paradoxical, but it reflects the design goal of making the license itself simple and modular. Instead, the license \emph{defers} to the smart contract that invoked it in the first place. If that contract says that the license has been revoked, it has been. - -The reasoning for this design choice can be illustrated by considering the \eiplicense interface, which defines a generic \code{revokeLicense()} method that can be called by a \code{\_revoker} address designated by the creator of the license. Thus, the following are all possible: -\begin{itemize} -\item The \code{\_revoker} is the licensor. The license can only be revoked with the consent of the licensor. -\item The \code{\_revoker} is the licensee. The license can only be revoked with the consent of the licensee. -\item The \code{\_revoker} is the zero address. The license is irrevocable. -\item The \code{\_revoker} is a smart contract which can be invoked by anyone to call the \eiplicense \code{revokeLicense()} method after the passage of 30 days from the time the license is issued. The license is for a limited time. -\item The \code{\_revoker} is a smart contract which can be invoked to call the \eiplicense \code{revokeLicense()} method upon the payment of a specified amount to the licensee. The licensor has an option to buy out the licensee. -\end{itemize} -In other words, the \eiplicense is completely generic in supporting arbitrary licensing logic, and because the \iccclicense defers to the Licensing Process (e.g., smart contract), it is also completely generic. Once again, the license is designed to work with the \eiplicense interface, but it is drafted so that any technical process serving the same function can be used instead. - -\section{Sublicensing} - -The \iccclicense takes a similarly broad and deferential attitude toward sublicensing. In practice, sublicensing is likely to be particularly useful in two scenarios. First, the owner may wish to contract with others to produce goods embodying the work, like T-shirts or song downloads. Here, a sublicense is practically necessary in a world where people don't personally print T-shirts and host downloads, but instead hire professionals to do it for them. Second, sublicensing is necessary for many derivative works: producing an animated video series, for example, will require a sublicense to a production company. Thus, the license generally allows for the free sublicensing of any of the rights held by the NFT owner. - -The \iccclicense does not attempt to enforce the requirement that a sublicense be compatible with the license it is issued under. The sublicense need not be the \iccclicense; indeed, it usually will not be. It is up to the sublicensor to choose license terms that are appropriate and are within the scope of their own license. What the \eiplicense standard can do is ensure that a party checking the licensing status of an NFT can see all of the licenses and sublicenses in force. But in general, the sublicenses may be arbitrary human-readable documents, rather than standardized machine-readable ones; it is up to the human reading them to understand their terms. (Future licenses and technical standards may fill in more sublicensing options and provide technical mechanisms to describe them; we have not attempted to solve these problems here and now.) - -As with revocation, the \iccclicense allows for a sublicense to be recorded on the blockchain, and as with revocation, these sublicenses are specifically supported by the \eiplicense interface. Again, the support is generic; the sublicense must include a link to its terms, and the \eiplicense interface does not attempt to verify that the sublicense's terms are compatible with the license's terms. Indeed, the license does not require that all sublicenses be explicitly recorded in the smart contract; for a T-shirt vendor or a web host, this is overkill. - -Instead, the advantage of recording a sublicense on the blockchain is that it makes the sublicense transfer with the NFT. The \iccclicense provides that all sublicenses that have been recorded this way carry over and remain as sublicenses from the new owner of the NFT. Someone who wants to make a substantial investment in an NFT -- e.g., a filmmaker creating a movie based on an NFT-licensed work -- can record this license using an \eiplicense smart contract. This puts everyone in the world (including potential buyers of the NFT) on notice of their sublicense and that it will carry over. (This is similar to how recording systems for copyrights and real property work, and the \eiplicense interface has been designed to make this notice straightforward.) Sublicenses do not themselves need to be NFTs and typically will not be, although it is possible to tokenize one if desired. - - - -\section{Boilerplate} - -The \iccclicense includes a section advising courts on how to interpret it in the event of a dispute. Although the license itself does not always force the copyright license to keep in sync with ownership on the blockchain, it advises courts that maintaining the reliability of blockchain records is an important goal, encouraging them to pick interpretations that make the blockchain a useful and authoritative source for understanding the status of a copyright license. In addition, the interpretation clause encourages courts to promote other common legal policies, such as fairness, uniformity, and predictability. - -The severability clause and the disclaimer of warranties and limitation of liability are based on the text of the Creative Commons license suite. These licenses are also intended to create licenses in members of the public (including people who may be unknown to the licensor), so their use cases are broadly similar. Several other clauses and definitions, including the disclaimer at the start of the license text, are also adapted from language used in the Creative Commons licenses. - -\appendix - -\section{Human-Readable Summary} -\label{sec:human} - -This is a human-readable summary of the Token-Bound NFT [\commercial{} | \noncommercial{}] [\derivative{} | \derivativetracking{} | \sharealike{} | \noderivative{}] [\publiclicense{} | \nopubliclicense{}] [\ledger{} | \legal{}] License. It is not a substitute for the license, and you should carefully review the license to understand its terms and how they may apply to you. - -This license gives you the following rights to use a creative work associated with an NFT on a ledger (such as a blockchain): - -\begin{itemize} -\item \textbf{Use and Share}: You can make copies of the work, use them personally, and share them with others \iflicenseoption{\noncommercial}{, but not for commercial purposes}. -\item \ifnotlicenseoption{\noderivative}{\textbf{Derivatives}: you can make new works that include and build on the work, use them, and share them with others \iflicenseoption{\noncommercial}{, but not for commercial purposes} \iflicenseoption{\derivativetracking}{, provided that you also release them as NFTs using the same technical standard and on the same ledger as the original NFT \iflicenseoption{\sharealike}{under the same license}}.} -\item \textbf{Ownership}: You can use the work to show that you own the NFT. -\item \textbf{Sale}: If you sell the NFT, you can use the work to show people what the NFT is. -\end{itemize} - -You have these rights as long as you own the NFT, but when you sell or transfer the NFT, your rights will end and the new owner will have these rights instead. \iflicenseoption{\ledger}{For purposes of this license, the owner of the NFT is whoever the ledger says owns it. This means that if the NFT is stolen, your rights under this license will pass to the new owner.} \iflicenseoption{\legal}{For purposes of this license, the owner of the NFT is whoever the legal system says owns it. This means that if the NFT is stolen, you will retain your rights under this license.} - -Your rights to the work are not guaranteed to be exclusive. The copyright owner who made an NFT of the work may have the right to allow others to use the work, as well. - -\iflicenseoption{\publiclicense}{In addition, the copyright owner has given everyone the right to use and share the work, regardless of whether they own the NFT.} - -This license terminates if the technical process that created it says that it has been terminated. You should check that process to see whether the license is still in effect and how it can be terminated. If this license does terminate, your rights under it will end, and you must stop using the work. - - -\section{License Text} -\label{sec:text} - -\begin{sffamily} - -\emph{The Initiative for Cryptocurrencies and Contracts (IC3) and Coalition of Automated Legal Applications (COALA) are not law firms and do not provide legal services or legal advice. Publication of the text of the \iccclicense does not create a lawyer-client or other relationship. IC3 and COALA make this text and related information available on an ``as-is'' basis. IC3 and COALA give no warranties regarding this license, any material licensed under its terms and conditions, or any related information. IC3 and COALA disclaim all liability for damages resulting from their use to the fullest extent possible.}\\\\ -The \iccclicense (the ``License'') grants you certain intellectual property rights with respect to Licensed Material associated with an NFT. When you acquire the NFT, you own the personal property rights to the token underlying the NFT (e.g., the right to freely sell, transfer, or otherwise dispose of that NFT), but you do not own the associated Licensed Material. Instead, the Licensor grants you a specified limited license to use the Licensed Material, as set forth below: - -\sect{Definitions} - -\subsect{Ledgers and NFTs} - - - \begin{itemize} - \item A ``Unique Identifier'' is information that is sufficient to distinguish one digital record from other digital records. - - \item A ``Ledger'' is a blockchain, database, or other digital system that records information about Unique Identifiers. - - \item An ``NFT'' is a Unique Identifier recorded in a Ledger. - - \item A ``Private Key'' is a cryptographic key, the use of which is necessary to modify the information about a Unique Identifier recorded in a Ledger. - - \item An NFT is ``Associated'' with a person or entity when that person or entity has substantially exclusive control over the Private Key necessary to modify the information about that Unique Identifier in that Ledger. - - \item The ``Controller'' of an NFT is the person or entity with whom the NFT is Associated. - - \end{itemize} - - \subsect{Licensing} - - \begin{itemize} - \item ``Copyright and Similar Rights'' means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and sui generis database rights, without regard to how the rights are labeled or categorized. - - \item ``License'' means the intellectual property license granted under the terms of this document. - - \item ``Licensed Rights'' means the rights granted to You subject to the terms and conditions of this License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. - - \item A ``Licensing Process'' is a technical process (including a smart contract that implements \eiplicense or any update, revision, new version, or successor thereof) designed to allow authorized parties to specify the current status of intellectual property license terms associated with an NFT and any related material, including the text of a license. A Licensing Process can (but need not) specify whether the license has been sublicensed, transferred, and/or revoked. - - \item An NFT ``Invokes'' this License when the NFT refers to the verbatim text of this License by means of a Licensing Process. - - \item The ``Licensor'' means the individual or entity that causes an NFT to Invoke this License. - - \item The ``License NFT'' is the NFT that Invokes this License. If more than one NFT Invokes this License, each such NFT is a separate License NFT resulting in a separate and distinct license grant with respect to the Licensed Material respectively associated with each such NFT. - - \end{itemize} - - \subsect{Licensed and Adapted Material} - - \begin{itemize} - - \item An NFT is ``Linked'' to material when the NFT contains a description of or hyperlink to that material, and that description or hyperlink is substantially immutable in the ordinary course of operation of the Ledger, - - \item ``Licensed Material'' means the creative work or other material to which the License NFT is Linked. - - \item ``Adapted Material'' means material subject to copyright and similar rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the copyright and similar rights held by the Licensor. For purposes of this License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. For purposes of this License, the exact reproduction of the Licensed Material in a different medium in a manner not requiring original creative effort (such as printing a photographic or pictorial work on paper) does not produce Adapted Material. - - \item ``Commercial'' means primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this License, activity is Commercial if direct or indirect payment is required as a condition of access to the Licensed Material or Adapted Material. For purposes of this License, the sale and advertising for sale of the License NFT (including the accompanying rights granted under this License) is not Commercial. - - \item ``Non-Commercial'' means not Commercial. - - \item To ``Use'' material is to copy, display, distribute, make available to the public, or perform it. - - \end{itemize} - - \subsect{Ownership and Transfers} - - \begin{itemize} - - \item The ``Owner'' of an NFT is \iflicenseoption{\legal}{the person or entity who is legally entitled to be its Controller} \iflicenseoption{\ledger}{its Controller}. - - \item ``You'' and ``Your'' refer to the person receiving rights under this License as the Owner of the License NFT. - - \item An NFT ``Revokes'' this License when the NFT indicates that it has been revoked by means of a Licensing Process. - - \item An NFT is ``Sublicensed'' when the NFT indicates that it has been sublicensed by means of a Licensing Process. - - \item \iflicenseoption{\derivativetracking}{Adapted Material is ``Derivative Tracked'' from an NFT (the ``Parent NFT'') when the Parent NFT is Sublicensed by means of a Licensing Process that (1) creates another NFT (the ``Child NFT'') on the same Ledger as the Parent NFT, (2) causes the Child NFT to be Linked to Adapted Material, and (3) causes the Child NFT and Parent NFT to have materially the same properties and functionality, except for the identity of the parties they are Associated with and the identity of the material they are Linked to.} - - \item \iflicenseoption{\sharealike}{Adapted Material is ``Share-Alike Sublicensed'' from an NFT (the ``Parent NFT'') when the Parent NFT is Sublicensed by means of a Licensing Process that (1) creates another NFT (the ``Child NFT'') on the same Ledger as the Parent NFT, (2) causes the Child NFT to be Linked to Adapted Material, (3) causes the Child NFT and Parent NFT to have materially the same properties and functionality, except for the identity of the parties they are Associated with and the identity of the material they are Linked to, and (4) causes the Child NFT to Invoke this license.} - - \item To ``Transfer'' an NFT is to change, modify, or update the Ledger such that the identity of the Owner of that NFT changes, by any legally sufficient means, including sale, barter, gift, bequest, or operation of law. - - \item ``Grace Period Process'' means an interface, function, method, or similar technical process that is part of a Licensing Process and which, when invoked, indicates whether a specified limited duration following the Transfer of an NFT has elapsed. - - \item ``Grace Period'' means the time following the Transfer of an NFT during which the Grace Period Process indicates that the specified limited duration has not yet elapsed. - - \end{itemize} - - -\sect{Public License Grant} - -\iflicenseoption{\publiclicense}{The Licensed Material is made available to the public under the terms of the Creative Commons Attribution-NoDerivatives 4.0 International Public License at \href{https://creativecommons.org/licenses/by-nd/4.0/legalcode}{https://creativecommons.org/licenses/by-nd/4.0/legalcode}.} - -\sect{NFT License Grant} - -Subject to the terms and conditions of this License, and on the condition that You are the Owner of the License NFT, the Licensor hereby grants to You a worldwide, royalty-free, sublicensable, non-exclusive, license to exercise the Licensed Rights to: -\begin{itemize} -\item Use the Licensed Material \iflicenseoption{\noncommercial}{for Non-Commercial purposes only}, -\item \ifnotlicenseoption{\noderivative}{Create and Use Adapted Material \iflicenseoption{\noncommercial}{for Non-Commercial purposes only} \iflicenseoption{\derivativetracking}{provided that the Adapted Material is Derivative Tracked from the License NFT} \iflicenseoption{\sharealike}{provided that the Adapted Material is Share-Alike Sublicensed from the License NFT}}, -\item Identify You as the Owner of the License NFT, -\item Use the Licensed Material in connection with the sale and advertising for sale of the License NFT, -\end{itemize} -This License becomes effective when the Licensor causes the License NFT to Invoke it. Because this is a unilateral license grant and not a contract, Your assent is not required for it to become effective. \iflicenseoption{\publiclicense}{This license grant is in addition to any rights granted to You as a member of the public under the terms of the Public License Grant above.} - -All rights granted under this License last for the full duration of the Licensor's Licensed Rights, except that if the License NFT Revokes this License, Your rights under this license grant will terminate, as will all sublicenses granted hereunder. - -This license grant is non-transferable. If the License NFT is Transferred such that You are no longer the Owner, Your rights under this license grant will terminate. Notwithstanding the previous sentence, if the Licensing Process contains a Grace Period Process, You may continue to Use the Licensed Material and any already-existing Adapted Material under the terms of the license grant above during the Grace Period, but You may not Create or Use new Adapted Material, grant new sublicenses, or Use the Licensed Material to identify yourself as the owner of the License NFT. - -If the License NFT is Transferred, any sublicenses granted hereunder that are Sublicensed will continue in force as sublicenses from the new Owner. If You have become the Owner of the License NFT as a result of a Transfer, you automatically grant any such sublicenses that are Sublicensed. This License does not by itself require you to continue or grant any sublicenses given by a previous Owner that were not Sublicensed, but it does not prevent other law from doing so. - - -\sect{Interpretation} - -By adopting this License, the Licensor intends to enhance the clarity and predictability of intellectual-property licensing via digital transactions. In cases of doubt or ambiguity, the terms of this license should be interpreted and applied to promote the goals of (1) making the information in the Digital System accurately reflect the licensing relationships it describes, and vice versa, (2) providing clear, simple, and unambiguous mechanisms for parties to express their licensing intentions, (3) protecting parties from duress, fraud, forgery, and mistake, and (4) achieving uniformity and consistency in the application of this License to different transactions, at different times, and in different jurisdictions. - -To the extent possible, if any provision of this License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this License without affecting the enforceability of the remaining terms. - -\sect{Disclaimer of Warranties and Limitation of Liability} - -Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. - -To the extent possible, unless otherwise separately undertaken by the Licensor, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. - -The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. - -\end{sffamily} - -\end{document} diff --git a/assets/eip-5218/license-tree.png b/assets/eip-5218/license-tree.png deleted file mode 100644 index 088cae0..0000000 Binary files a/assets/eip-5218/license-tree.png and /dev/null differ diff --git a/assets/eip-5219/IDecentralizedApp.sol b/assets/eip-5219/IDecentralizedApp.sol deleted file mode 100644 index c25dbae..0000000 --- a/assets/eip-5219/IDecentralizedApp.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -struct KeyValue { - string key; - string value; -} - -interface IDecentralizedApp { - /// @notice Send an HTTP GET-like request to this contract - /// @param resource The resource to request (e.g. "/asdf/1234" turns in to `["asdf", "1234"]`) - /// @param params The query parameters. (e.g. "?asdf=1234&foo=bar" turns in to `[{ key: "asdf", value: "1234" }, { key: "foo", value: "bar" }]`) - /// @return statusCode The HTTP status code (e.g. 200) - /// @return body The body of the response - /// @return headers A list of header names (e.g. [{ key: "Content-Type", value: "application/json" }]) - function request(string[] memory resource, KeyValue[] memory params) external view returns (uint8 statusCode, string memory body, KeyValue[] headers); -} diff --git a/assets/eip-5247/ProposalRegistry.sol b/assets/eip-5247/ProposalRegistry.sol deleted file mode 100644 index 76f56f2..0000000 --- a/assets/eip-5247/ProposalRegistry.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT -// A fully runnalbe version can be found in https://github.com/ercref/ercref-contracts/tree/869843f23dc4da793f0d9d018ed92e3950da8f75 -pragma solidity ^0.8.17; - -import "./IERC5247.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -struct Proposal { - address by; - uint256 proposalId; - address[] targets; - uint256[] values; - uint256[] gasLimits; - bytes[] calldatas; -} - -contract ProposalRegistry is IERC5247 { - using Address for address; - mapping(uint256 => Proposal) public proposals; - uint256 private proposalCount; - function createProposal( - uint256 proposalId, - address[] calldata targets, - uint256[] calldata values, - uint256[] calldata gasLimits, - bytes[] calldata calldatas, - bytes calldata extraParams - ) external returns (uint256 registeredProposalId) { - require(targets.length == values.length, "GeneralForwarder: targets and values length mismatch"); - require(targets.length == gasLimits.length, "GeneralForwarder: targets and gasLimits length mismatch"); - require(targets.length == calldatas.length, "GeneralForwarder: targets and calldatas length mismatch"); - registeredProposalId = proposalCount; - proposalCount++; - - proposals[registeredProposalId] = Proposal({ - by: msg.sender, - proposalId: proposalId, - targets: targets, - values: values, - calldatas: calldatas, - gasLimits: gasLimits - }); - emit ProposalCreated(msg.sender, proposalId, targets, values, gasLimits, calldatas, extraParams); - return registeredProposalId; - } - function executeProposal(uint256 proposalId, bytes calldata extraParams) external { - Proposal storage proposal = proposals[proposalId]; - address[] memory targets = proposal.targets; - string memory errorMessage = "Governor: call reverted without message"; - for (uint256 i = 0; i < targets.length; ++i) { - (bool success, bytes memory returndata) = proposal.targets[i].call{value: proposal.values[i]}(proposal.calldatas[i]); - Address.verifyCallResult(success, returndata, errorMessage); - } - emit ProposalExecuted(msg.sender, proposalId, extraParams); - } - - function getProposal(uint256 proposalId) external view returns (Proposal memory) { - return proposals[proposalId]; - } - - function getProposalCount() external view returns (uint256) { - return proposalCount; - } - -} diff --git a/assets/eip-5247/testProposalRegistry.ts b/assets/eip-5247/testProposalRegistry.ts deleted file mode 100644 index 947c40b..0000000 --- a/assets/eip-5247/testProposalRegistry.ts +++ /dev/null @@ -1,162 +0,0 @@ -// A fully runnalbe version can be found in https://github.com/ercref/ercref-contracts/tree/869843f23dc4da793f0d9d018ed92e3950da8f75 -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { hexlify } from "ethers/lib/utils"; -import { ethers } from "hardhat"; - -describe("ProposalRegistry", function () { - async function deployFixture() { - // Contracts are deployed using the first signer/account by default - const [owner, otherAccount] = await ethers.getSigners(); - - const ProposalRegistry = await ethers.getContractFactory("ProposalRegistry"); - const contract = await ProposalRegistry.deploy(); - - const ERC721ForTesting = await ethers.getContractFactory("ERC721ForTesting"); - const erc721 = await ERC721ForTesting.deploy(); - - const SimpleForwarder = await ethers.getContractFactory("SimpleForwarder"); - const forwarder = await SimpleForwarder.deploy(); - return { contract, erc721, forwarder, owner, otherAccount }; - } - - describe("Deployment", function () { - it("Should work for a simple case", async function () { - const { contract, erc721, owner } = await loadFixture(deployFixture); - const callData1 = erc721.interface.encodeFunctionData("mint", [owner.address, 1]); - const callData2 = erc721.interface.encodeFunctionData("mint", [owner.address, 2]); - await contract.connect(owner) - .createProposal( - 0, - [erc721.address, erc721.address], - [0,0], - [0,0], - [callData1, callData2], - []); - expect(await erc721.balanceOf(owner.address)).to.equal(0); - await contract.connect(owner).executeProposal(0, []); - expect(await erc721.balanceOf(owner.address)).to.equal(2); - }); - const Ns = [0, 50, 100, 150, 200]; - for (let n of Ns) { - - it(`Should work for a proposal case of ${n}`, async function () { - const { contract, erc721, owner } = await loadFixture(deployFixture); - const numOfMint = n; - const calldatas = []; - for (let i = 0 ; i < numOfMint; i++) { - const callData = erc721.interface.encodeFunctionData("mint", [owner.address, i]); - calldatas.push(callData); - } - let txCreate = await contract.connect(owner) - .createProposal( - 0, - Array(numOfMint).fill(erc721.address), - Array(numOfMint).fill(0), - Array(numOfMint).fill(0), - calldatas, - []); - let txCreateWaited = await txCreate.wait(); - console.log(`Creation TX gas`, txCreateWaited.cumulativeGasUsed.toString()); - console.log(`Gas per mint`, parseInt(txCreateWaited.cumulativeGasUsed.toString()) / numOfMint); - expect(await erc721.balanceOf(owner.address)).to.equal(0); - let txExecute = await contract.connect(owner).executeProposal(0, []); - let txExecuteWaited = await txExecute.wait(); - console.log(`Execution TX gas`, txExecuteWaited.cumulativeGasUsed.toString()); - console.log(`Gas per mint`, parseInt(txExecuteWaited.cumulativeGasUsed.toString()) / numOfMint); - expect(await erc721.balanceOf(owner.address)).to.equal(numOfMint); - }); - } - }); - describe("Benchmark", function () { - it(`Should work for a forwarding case`, async function () { - const { forwarder, erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 200; - const calldatas = []; - for (let i = 0 ; i < numOfMint; i++) { - const callData = erc721.interface.encodeFunctionData("mint", [owner.address, i]); - calldatas.push(callData); - } - expect(await erc721.balanceOf(owner.address)).to.equal(0); - let txForward = await forwarder.connect(owner) - .forward( - Array(numOfMint).fill(erc721.address), - Array(numOfMint).fill(0), - Array(numOfMint).fill(0), - calldatas); - let txForwardWaited = await txForward.wait(); - - console.log(`txForwardWaited TX gas`, txForwardWaited.cumulativeGasUsed.toString()); - - console.log(`Gas per mint`, parseInt(txForwardWaited.cumulativeGasUsed.toString()) / numOfMint); - expect(await erc721.balanceOf(owner.address)).to.equal(numOfMint); - - }); - - - it(`Should work for erc721 batchMint with same addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 200; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(owner.address);// addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }) - - it(`Should work for erc721 batchMint with different addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 200; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }); - - - it(`Should work for erc721 batchSafeMint with same addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 400; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(owner.address);// addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchSafeMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchSafeMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }); - - it(`Should work for erc721 batchSafeMint with different addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 400; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchSafeMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchSafeMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} the Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }); - }); -}); diff --git a/assets/eip-5252/.gitignore b/assets/eip-5252/.gitignore deleted file mode 100644 index b41feef..0000000 --- a/assets/eip-5252/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -node_modules -.env -coverage -coverage.json -typechain -typechain-types -yarn.lock -package-lock.json -yarn-error.log -.DS_Store - -#Hardhat files -cache -artifacts diff --git a/assets/eip-5252/README.md b/assets/eip-5252/README.md deleted file mode 100644 index 5136b9b..0000000 --- a/assets/eip-5252/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# EIP 5252 implementation - -This project is a reference implementation of EIP-5252. - -Try running some of the following tasks: - -```shell -npx hardhat help -npx hardhat test -GAS_REPORT=true npx hardhat test -npx hardhat node -npx hardhat run scripts/deploy.ts -``` diff --git a/assets/eip-5252/contracts/ABT.sol b/assets/eip-5252/contracts/ABT.sol deleted file mode 100644 index a9896e0..0000000 --- a/assets/eip-5252/contracts/ABT.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC721A.sol"; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "./interfaces/IFactory.sol"; -import "./interfaces/IFinance.sol"; -import "./interfaces/IDescriptor.sol"; - -contract ABT is ERC721A, AccessControl { - // Create a new role identifier for the minter role - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); - // factory address - address public factory; - // SVG for ABT - address public descriptor; - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721A, AccessControl) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function setDescriptor(address descriptor_) public { - require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "ABT: Caller is not a default admin"); - descriptor = descriptor_; - } - - function tokenURI(uint256 tokenId) public view virtual override returns (string memory tokenURI) { - require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - tokenURI = IDescriptor(descriptor).tokenURI(tokenId); - } - - constructor(address factory_) - ERC721A("Account-Bound Token", "ABT") { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - - _setupRole(MINTER_ROLE, _msgSender()); - _setupRole(BURNER_ROLE, _msgSender()); - factory = factory_; - } - - function setFactory(address factory_) public { - require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "ABT: Caller is not a default admin"); - factory = factory_; - } - - function mint(address to) external { - // Check that the calling account has the minter role - require(_msgSender() == factory, "ABT: Caller is not factory"); - _safeMint(to, 1); - } - - function burn(uint256 tokenId_) external { - require(hasRole(BURNER_ROLE, _msgSender()), "ABT: must have burner role to burn"); - _burn(tokenId_); - } - - function exists(uint256 tokenId_) external view returns (bool) { - return _exists(tokenId_); - } - - function transfer( - address to, - uint256 tokenId - ) public virtual { - transferFrom(msg.sender, to, tokenId); - } -} diff --git a/assets/eip-5252/contracts/ERC721A.sol b/assets/eip-5252/contracts/ERC721A.sol deleted file mode 100644 index 92ebd12..0000000 --- a/assets/eip-5252/contracts/ERC721A.sol +++ /dev/null @@ -1,616 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -// Creator: Chiru Labs - -pragma solidity ^0.8.4; - -import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; -import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; -import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol'; -import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol'; -import '@openzeppelin/contracts/utils/Address.sol'; -import '@openzeppelin/contracts/utils/Context.sol'; -import '@openzeppelin/contracts/utils/Strings.sol'; -import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; - -error ApprovalCallerNotOwnerNorApproved(); -error ApprovalQueryForNonexistentToken(); -error ApproveToCaller(); -error ApprovalToCurrentOwner(); -error BalanceQueryForZeroAddress(); -error MintedQueryForZeroAddress(); -error BurnedQueryForZeroAddress(); -error AuxQueryForZeroAddress(); -error MintToZeroAddress(); -error MintZeroQuantity(); -error OwnerIndexOutOfBounds(); -error OwnerQueryForNonexistentToken(); -error TokenIndexOutOfBounds(); -error TransferCallerNotOwnerNorApproved(); -error TransferFromIncorrectOwner(); -error TransferToNonERC721ReceiverImplementer(); -error TransferToZeroAddress(); -error URIQueryForNonexistentToken(); - -/** - * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including - * the Metadata extension. Built to optimize for lower gas during batch mints. - * - * Assumes serials are sequentially minted starting at _startTokenId() (defaults to 0, e.g. 0, 1, 2, 3..). - * - * Assumes that an owner cannot have more than 2**64 - 1 (max value of uint64) of supply. - * - * Assumes that the maximum token id cannot exceed 2**256 - 1 (max value of uint256). - */ -contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { - using Address for address; - using Strings for uint256; - - // Compiler will pack this into a single 256bit word. - struct TokenOwnership { - // The address of the owner. - address addr; - // Keeps track of the start time of ownership with minimal overhead for tokenomics. - uint64 startTimestamp; - // Whether the token has been burned. - bool burned; - } - - // Compiler will pack this into a single 256bit word. - struct AddressData { - // Realistically, 2**64-1 is more than enough. - uint64 balance; - // Keeps track of mint count with minimal overhead for tokenomics. - uint64 numberMinted; - // Keeps track of burn count with minimal overhead for tokenomics. - uint64 numberBurned; - // For miscellaneous variable(s) pertaining to the address - // (e.g. number of whitelist mint slots used). - // If there are multiple variables, please pack them into a uint64. - uint64 aux; - } - - // The tokenId of the next token to be minted. - uint256 internal _currentIndex; - - // The number of tokens burned. - uint256 internal _burnCounter; - - // Token name - string private _name; - - // Token symbol - string private _symbol; - - // Mapping from token ID to ownership details - // An empty struct value does not necessarily mean the token is unowned. See ownershipOf implementation for details. - mapping(uint256 => TokenOwnership) internal _ownerships; - - // Mapping owner address to address data - mapping(address => AddressData) private _addressData; - - // Mapping from token ID to approved address - mapping(uint256 => address) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - _currentIndex = _startTokenId(); - } - - /** - * To change the starting tokenId, please override this function. - */ - function _startTokenId() internal view virtual returns (uint256) { - return 0; - } - - /** - * @dev See {IERC721Enumerable-totalSupply}. - * @dev Burned tokens are calculated here, use _totalMinted() if you want to count just minted tokens. - */ - function totalSupply() public view returns (uint256) { - // Counter underflow is impossible as _burnCounter cannot be incremented - // more than _currentIndex - _startTokenId() times - unchecked { - return _currentIndex - _burnCounter - _startTokenId(); - } - } - - /** - * Returns the total amount of tokens minted in the contract. - */ - function _totalMinted() internal view returns (uint256) { - // Counter underflow is impossible as _currentIndex does not decrement, - // and it is initialized to _startTokenId() - unchecked { - return _currentIndex - _startTokenId(); - } - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC721Metadata).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev See {IERC721-balanceOf}. - */ - function balanceOf(address owner) public view override returns (uint256) { - if (owner == address(0)) revert BalanceQueryForZeroAddress(); - return uint256(_addressData[owner].balance); - } - - /** - * Returns the number of tokens minted by `owner`. - */ - function _numberMinted(address owner) internal view returns (uint256) { - if (owner == address(0)) revert MintedQueryForZeroAddress(); - return uint256(_addressData[owner].numberMinted); - } - - /** - * Returns the number of tokens burned by or on behalf of `owner`. - */ - function _numberBurned(address owner) internal view returns (uint256) { - if (owner == address(0)) revert BurnedQueryForZeroAddress(); - return uint256(_addressData[owner].numberBurned); - } - - /** - * Returns the auxiliary data for `owner`. (e.g. number of whitelist mint slots used). - */ - function _getAux(address owner) internal view returns (uint64) { - if (owner == address(0)) revert AuxQueryForZeroAddress(); - return _addressData[owner].aux; - } - - /** - * Sets the auxiliary data for `owner`. (e.g. number of whitelist mint slots used). - * If there are multiple variables, please pack them into a uint64. - */ - function _setAux(address owner, uint64 aux) internal { - if (owner == address(0)) revert AuxQueryForZeroAddress(); - _addressData[owner].aux = aux; - } - - /** - * Gas spent here starts off proportional to the maximum mint batch size. - * It gradually moves to O(1) as tokens get transferred around in the collection over time. - */ - function ownershipOf(uint256 tokenId) internal view returns (TokenOwnership memory) { - uint256 curr = tokenId; - - unchecked { - if (_startTokenId() <= curr && curr < _currentIndex) { - TokenOwnership memory ownership = _ownerships[curr]; - if (!ownership.burned) { - if (ownership.addr != address(0)) { - return ownership; - } - // Invariant: - // There will always be an ownership that has an address and is not burned - // before an ownership that does not have an address and is not burned. - // Hence, curr will not underflow. - while (true) { - curr--; - ownership = _ownerships[curr]; - if (ownership.addr != address(0)) { - return ownership; - } - } - } - } - } - revert OwnerQueryForNonexistentToken(); - } - - /** - * @dev See {IERC721-ownerOf}. - */ - function ownerOf(uint256 tokenId) public view override returns (address) { - return ownershipOf(tokenId).addr; - } - - /** - * @dev See {IERC721Metadata-name}. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev See {IERC721Metadata-symbol}. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev See {IERC721Metadata-tokenURI}. - */ - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); - - string memory baseURI = _baseURI(); - return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ''; - } - - /** - * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each - * token will be the concatenation of the `baseURI` and the `tokenId`. Empty - * by default, can be overridden in child contracts. - */ - function _baseURI() internal view virtual returns (string memory) { - return ''; - } - - /** - * @dev See {IERC721-approve}. - */ - function approve(address to, uint256 tokenId) public override { - address owner = ERC721A.ownerOf(tokenId); - if (to == owner) revert ApprovalToCurrentOwner(); - - if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) { - revert ApprovalCallerNotOwnerNorApproved(); - } - - _approve(to, tokenId, owner); - } - - /** - * @dev See {IERC721-getApproved}. - */ - function getApproved(uint256 tokenId) public view override returns (address) { - if (!_exists(tokenId)) revert ApprovalQueryForNonexistentToken(); - - return _tokenApprovals[tokenId]; - } - - /** - * @dev See {IERC721-setApprovalForAll}. - */ - function setApprovalForAll(address operator, bool approved) public override { - if (operator == _msgSender()) revert ApproveToCaller(); - - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @dev See {IERC721-isApprovedForAll}. - */ - function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { - return _operatorApprovals[owner][operator]; - } - - /** - * @dev See {IERC721-transferFrom}. - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - _transfer(from, to, tokenId); - } - - /** - * @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - safeTransferFrom(from, to, tokenId, ''); - } - - /** - * @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) public virtual override { - _transfer(from, to, tokenId); - if (to.isContract() && !_checkContractOnERC721Received(from, to, tokenId, _data)) { - revert TransferToNonERC721ReceiverImplementer(); - } - } - - /** - * @dev Returns whether `tokenId` exists. - * - * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. - * - * Tokens start existing when they are minted (`_mint`), - */ - function _exists(uint256 tokenId) internal view returns (bool) { - return _startTokenId() <= tokenId && tokenId < _currentIndex && - !_ownerships[tokenId].burned; - } - - function _safeMint(address to, uint256 quantity) internal { - _safeMint(to, quantity, ''); - } - - /** - * @dev Safely mints `quantity` tokens and transfers them to `to`. - * - * Requirements: - * - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called for each safe transfer. - * - `quantity` must be greater than 0. - * - * Emits a {Transfer} event. - */ - function _safeMint( - address to, - uint256 quantity, - bytes memory _data - ) internal { - _mint(to, quantity, _data, true); - } - - /** - * @dev Mints `quantity` tokens and transfers them to `to`. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - `quantity` must be greater than 0. - * - * Emits a {Transfer} event. - */ - function _mint( - address to, - uint256 quantity, - bytes memory _data, - bool safe - ) internal { - uint256 startTokenId = _currentIndex; - if (to == address(0)) revert MintToZeroAddress(); - if (quantity == 0) revert MintZeroQuantity(); - - _beforeTokenTransfers(address(0), to, startTokenId, quantity); - - // Overflows are incredibly unrealistic. - // balance or numberMinted overflow if current value of either + quantity > 1.8e19 (2**64) - 1 - // updatedIndex overflows if _currentIndex + quantity > 1.2e77 (2**256) - 1 - unchecked { - _addressData[to].balance += uint64(quantity); - _addressData[to].numberMinted += uint64(quantity); - - _ownerships[startTokenId].addr = to; - _ownerships[startTokenId].startTimestamp = uint64(block.timestamp); - - uint256 updatedIndex = startTokenId; - uint256 end = updatedIndex + quantity; - - if (safe && to.isContract()) { - do { - emit Transfer(address(0), to, updatedIndex); - if (!_checkContractOnERC721Received(address(0), to, updatedIndex++, _data)) { - revert TransferToNonERC721ReceiverImplementer(); - } - } while (updatedIndex != end); - // Reentrancy protection - if (_currentIndex != startTokenId) revert(); - } else { - do { - emit Transfer(address(0), to, updatedIndex++); - } while (updatedIndex != end); - } - _currentIndex = updatedIndex; - } - _afterTokenTransfers(address(0), to, startTokenId, quantity); - } - - /** - * @dev Transfers `tokenId` from `from` to `to`. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * - * Emits a {Transfer} event. - */ - function _transfer( - address from, - address to, - uint256 tokenId - ) private { - TokenOwnership memory prevOwnership = ownershipOf(tokenId); - - bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr || - isApprovedForAll(prevOwnership.addr, _msgSender()) || - getApproved(tokenId) == _msgSender()); - - if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved(); - if (prevOwnership.addr != from) revert TransferFromIncorrectOwner(); - if (to == address(0)) revert TransferToZeroAddress(); - - _beforeTokenTransfers(from, to, tokenId, 1); - - // Clear approvals from the previous owner - _approve(address(0), tokenId, prevOwnership.addr); - - // Underflow of the sender's balance is impossible because we check for - // ownership above and the recipient's balance can't realistically overflow. - // Counter overflow is incredibly unrealistic as tokenId would have to be 2**256. - unchecked { - _addressData[from].balance -= 1; - _addressData[to].balance += 1; - - _ownerships[tokenId].addr = to; - _ownerships[tokenId].startTimestamp = uint64(block.timestamp); - - // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. - // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. - uint256 nextTokenId = tokenId + 1; - if (_ownerships[nextTokenId].addr == address(0)) { - // This will suffice for checking _exists(nextTokenId), - // as a burned slot cannot contain the zero address. - if (nextTokenId < _currentIndex) { - _ownerships[nextTokenId].addr = prevOwnership.addr; - _ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp; - } - } - } - - emit Transfer(from, to, tokenId); - _afterTokenTransfers(from, to, tokenId, 1); - } - - /** - * @dev Destroys `tokenId`. - * The approval is cleared when the token is burned. - * - * Requirements: - * - * - `tokenId` must exist. - * - * Emits a {Transfer} event. - */ - function _burn(uint256 tokenId) internal virtual { - TokenOwnership memory prevOwnership = ownershipOf(tokenId); - - _beforeTokenTransfers(prevOwnership.addr, address(0), tokenId, 1); - - // Clear approvals from the previous owner - _approve(address(0), tokenId, prevOwnership.addr); - - // Underflow of the sender's balance is impossible because we check for - // ownership above and the recipient's balance can't realistically overflow. - // Counter overflow is incredibly unrealistic as tokenId would have to be 2**256. - unchecked { - _addressData[prevOwnership.addr].balance -= 1; - _addressData[prevOwnership.addr].numberBurned += 1; - - // Keep track of who burned the token, and the timestamp of burning. - _ownerships[tokenId].addr = prevOwnership.addr; - _ownerships[tokenId].startTimestamp = uint64(block.timestamp); - _ownerships[tokenId].burned = true; - - // If the ownership slot of tokenId+1 is not explicitly set, that means the burn initiator owns it. - // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. - uint256 nextTokenId = tokenId + 1; - if (_ownerships[nextTokenId].addr == address(0)) { - // This will suffice for checking _exists(nextTokenId), - // as a burned slot cannot contain the zero address. - if (nextTokenId < _currentIndex) { - _ownerships[nextTokenId].addr = prevOwnership.addr; - _ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp; - } - } - } - - emit Transfer(prevOwnership.addr, address(0), tokenId); - _afterTokenTransfers(prevOwnership.addr, address(0), tokenId, 1); - - // Overflow not possible, as _burnCounter cannot be exceed _currentIndex times. - unchecked { - _burnCounter++; - } - } - - /** - * @dev Approve `to` to operate on `tokenId` - * - * Emits a {Approval} event. - */ - function _approve( - address to, - uint256 tokenId, - address owner - ) private { - _tokenApprovals[tokenId] = to; - emit Approval(owner, to, tokenId); - } - - /** - * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target contract. - * - * @param from address representing the previous owner of the given token ID - * @param to target address that will receive the tokens - * @param tokenId uint256 ID of the token to be transferred - * @param _data bytes optional data to send along with the call - * @return bool whether the call correctly returned the expected magic value - */ - function _checkContractOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) private returns (bool) { - try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { - return retval == IERC721Receiver(to).onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert TransferToNonERC721ReceiverImplementer(); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - - /** - * @dev Hook that is called before a set of serially-ordered token ids are about to be transferred. This includes minting. - * And also called before burning one token. - * - * startTokenId - the first token id to be transferred - * quantity - the amount to be transferred - * - * Calling conditions: - * - * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be - * transferred to `to`. - * - When `from` is zero, `tokenId` will be minted for `to`. - * - When `to` is zero, `tokenId` will be burned by `from`. - * - `from` and `to` are never both zero. - */ - function _beforeTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual {} - - /** - * @dev Hook that is called after a set of serially-ordered token ids have been transferred. This includes - * minting. - * And also called after one token has been burned. - * - * startTokenId - the first token id to be transferred - * quantity - the amount to be transferred - * - * Calling conditions: - * - * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been - * transferred to `to`. - * - When `from` is zero, `tokenId` has been minted for `to`. - * - When `to` is zero, `tokenId` has been burned by `from`. - * - `from` and `to` are never both zero. - */ - function _afterTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual {} -} \ No newline at end of file diff --git a/assets/eip-5252/contracts/Factory.sol b/assets/eip-5252/contracts/Factory.sol deleted file mode 100644 index 335e01a..0000000 --- a/assets/eip-5252/contracts/Factory.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "./Finance.sol"; -import "./libraries/CloneFactory.sol"; -import "./interfaces/IFactory.sol"; - -contract Factory is AccessControl, IFactory { - // Vaults - address[] public allFinances; - /// Address of cdp nft registry - address public override abt; - /// Address of Wrapped Ether - address public override WETH; - /// Address of manager - address public override manager; - /// version number of impl - uint256 version; - /// address of vault impl - address public impl; - - constructor() { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - _createImpl(); - } - - /// Vault can issue stablecoin, it just manages the position - function createFinance( - address weth_, - uint256 amount_, - address recipient - ) external override returns (address vault, uint256 id) { - require(msg.sender == manager, "Factory: IA"); - uint256 gIndex = allFinancesLength(); - address proxy = CloneFactory._createClone(impl); - IFinance(proxy).initialize(manager, gIndex, abt, amount_, weth_); - allFinances.push(proxy); - IABT(abt).mint(recipient); - return (proxy, gIndex); - } - - // Set immutable, consistent, one rule for vault implementation - function _createImpl() internal { - address addr; - bytes memory bytecode = type(Finance).creationCode; - bytes32 salt = keccak256(abi.encodePacked("finance", version)); - assembly { - addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) - if iszero(extcodesize(addr)) { - revert(0, 0) - } - } - impl = addr; - } - - function isClone(address vault) external view returns (bool cloned) { - cloned = CloneFactory._isClone(impl, vault); - } - - function initialize( - address abt_, - address weth_, - address manager_, - uint256 version_ - ) public { - require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "IA"); // Invalid Access - abt = abt_; - WETH = weth_; - manager = manager_; - version = version_; - } - - function getFinance(uint256 financeId_) - external - view - override - returns (address) - { - return allFinances[financeId_]; - } - - function financeCodeHash() - external - pure - override - returns (bytes32 vaultCode) - { - return keccak256(hex"3d602d80600a3d3981f3"); - } - - function allFinancesLength() public view returns (uint256) { - return allFinances.length; - } -} diff --git a/assets/eip-5252/contracts/Finance.sol b/assets/eip-5252/contracts/Finance.sol deleted file mode 100644 index 69d4be5..0000000 --- a/assets/eip-5252/contracts/Finance.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./interfaces/IERC20Minimal.sol"; -import "./libraries/TransferHelper.sol"; -import "./interfaces/IFinance.sol"; -import "./interfaces/IABT.sol"; -import "./interfaces/IWETH.sol"; -import "./libraries/Initializable.sol"; -import "./interfaces/IManager.sol"; -import "./interfaces/IInfluencer.sol"; - -contract Finance is IFinance, Initializable { - /// Address of a manager - address public override manager; - /// Address of a factory - address public override factory; - /// Address of a factory - address public override influencer; - /// Address of account bound token - address public override abt; - /// Finance global identifier - uint256 public override financeId; - /// Address of wrapped eth - address public override WETH; - /// Finance Creation Date - uint256 public override createdAt; - /// Finance Last Updated Date - uint256 public override lastUpdated; - /// deposited amount to the account - uint256 public override deposit; - - modifier onlyFinanceOwner() { - require( - IABT(abt).ownerOf(financeId) == msg.sender, - "Finance: Finance is not owned by you" - ); - _; - } - - // called once by the factory at time of deployment - function initialize( - address manager_, - uint256 financeId_, - address abt_, - uint256 amount_, - address weth_ - ) external override initializer { - financeId = financeId_; - abt = abt_; - WETH = weth_; - manager = manager_; - factory = msg.sender; - deposit = amount_; - lastUpdated = block.timestamp; - createdAt = block.timestamp; - influencer = IManager(manager_).influencer(); - } - - function depositNative() external payable onlyFinanceOwner { - // wrap deposit - deposit += msg.value; - IInfluencer(influencer).deposit(msg.value); - IWETH(WETH).deposit{value: msg.value}(); - emit DepositFundNative(financeId, msg.value); - } - - /// Withdraw collateral as native currency - function withdrawNative(uint256 amount_) external virtual onlyFinanceOwner { - deposit -= amount_; - IInfluencer(influencer).withdraw(amount_); - // unwrap collateral - IWETH(WETH).withdraw(amount_); - // send withdrawn native currency - TransferHelper.safeTransferETH(msg.sender, address(this).balance); - emit WithdrawFundNative(financeId, amount_); - } - - receive() external payable { - assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract - } -} diff --git a/assets/eip-5252/contracts/Manager.sol b/assets/eip-5252/contracts/Manager.sol deleted file mode 100644 index 3b1142c..0000000 --- a/assets/eip-5252/contracts/Manager.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "./interfaces/IWETH.sol"; -import "./interfaces/IManager.sol"; -import "./interfaces/IFactory.sol"; -import "./interfaces/IERC20Minimal.sol"; - -contract Manager is AccessControl, IManager { - - // Configs - /// key: Collateral address, value: Liquidation Fee Ratio (LFR) in percent(%) with 5 decimal precision(100.00000%) - mapping (address => uint) internal ExampleConfig; - - address public override factory; - - constructor() { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - } - - function initializeConfig(address something, uint example) public { - require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "IA"); // Invalid Access - ExampleConfig[something] = example; - emit ConfigInitialized(something, example); - } - - function initialize(address stablecoin_, address factory_, address liquidator_) public { - require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "IA"); // Invalid Access - factory = factory_; - } - - function createFinanceNative(uint amount_) payable public returns(bool success) { - address WETH = IFactory(factory).WETH(); - // check validity - - // create vault - (address vlt, uint256 id) = IFactory(factory).createFinance(WETH, amount_, _msgSender()); - require(vlt != address(0), "VAULTMANAGER: FE"); // Factory error - // wrap native currency - IWETH(WETH).deposit{value: address(this).balance}(); - uint256 weth = IERC20Minimal(WETH).balanceOf(address(this)); - // then transfer collateral native currency to the finance contract, manage collateral from there. - require(IWETH(WETH).transfer(vlt, weth)); - emit FinanceCreated(id, WETH, msg.sender, vlt, msg.value); - return true; - } - - - function getExampleConfig(address something) external view override returns (uint) { - return ExampleConfig[something]; - } -} - diff --git a/assets/eip-5252/contracts/governance/Governor.sol b/assets/eip-5252/contracts/governance/Governor.sol deleted file mode 100644 index 5d06e65..0000000 --- a/assets/eip-5252/contracts/governance/Governor.sol +++ /dev/null @@ -1,109 +0,0 @@ -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/governance/Governor.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; -import "../interfaces/IInfluencer.sol"; -import "@openzeppelin/contracts/governance/utils/IVotes.sol"; - -contract MyGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorTimelockControl { - constructor(IVotes _token, TimelockController _timelock) - Governor("MyGovernor") - GovernorSettings(1 /* 1 block */, 45818 /* 1 week */, 0) - GovernorVotes(_token) - GovernorVotesQuorumFraction(4) - GovernorTimelockControl(_timelock) - {} - - // The following functions are overrides required by Solidity. - - function votingDelay() - public - view - override(IGovernor, GovernorSettings) - returns (uint256) - { - return super.votingDelay(); - } - - function votingPeriod() - public - view - override(IGovernor, GovernorSettings) - returns (uint256) - { - return super.votingPeriod(); - } - - function quorum(uint256 blockNumber) - public - view - override(IGovernor, GovernorVotesQuorumFraction) - returns (uint256) - { - return super.quorum(blockNumber); - } - - function state(uint256 proposalId) - public - view - override(Governor, GovernorTimelockControl) - returns (ProposalState) - { - return super.state(proposalId); - } - - function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description) - public - override(Governor, IGovernor) - returns (uint256) - { - // check if sender is enforcer - return super.propose(targets, values, calldatas, description); - } - - function proposalThreshold() - public - view - override(Governor, GovernorSettings) - returns (uint256) - { - return super.proposalThreshold(); - } - - function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) - internal - override(Governor, GovernorTimelockControl) - { - super._execute(proposalId, targets, values, calldatas, descriptionHash); - } - - function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) - internal - override(Governor, GovernorTimelockControl) - returns (uint256) - { - return super._cancel(targets, values, calldatas, descriptionHash); - } - - function _executor() - internal - view - override(Governor, GovernorTimelockControl) - returns (address) - { - return super._executor(); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, GovernorTimelockControl) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5252/contracts/governance/Influencer.sol b/assets/eip-5252/contracts/governance/Influencer.sol deleted file mode 100644 index d34000f..0000000 --- a/assets/eip-5252/contracts/governance/Influencer.sol +++ /dev/null @@ -1,44 +0,0 @@ -pragma solidity ^0.8.0; - -import "../interfaces/IABT.sol"; -import "../interfaces/IFactory.sol"; -import "../interfaces/IFinance.sol"; -import "../interfaces/IERC20Minimal.sol"; - -contract Influencer { - - uint256 totalContributionValue; - - mapping(string => Weight) weights; - - struct Weight { - uint256 percentage; - uint256 decimal; - } - - function getInfluence(address abt_, uint256 id_) public returns (uint multiplier) { - return _getInfluence(abt_, id_); - } - - - function _getInfluence(address abt_, uint256 id_) internal returns (uint influence) { - // get Finance address - address factory = IABT(abt_).factory(); - address finance = IFactory(factory).getFinance(id_); - address WETH = IFinance(finance).WETH(); - // normalize finance value - uint256 norm_alpha = IERC20Minimal(WETH).balanceOf(finance) / totalContributionValue * 100; - uint256 norm_beta = block.timestamp - IFinance(finance).createdAt() / block.timestamp * 100; - - // Divide with each decimal - uint256 influence_dec = weights["alpha"].percentage * norm_alpha + weights["beta"].percentage * norm_beta; - return influence_dec / weights["alpha"].decimal / weights["beta"].decimal; - } - - function setWeight(string memory key, uint256 percentage, uint256 decimal) public { - weights[key] = Weight({ - percentage: percentage, - decimal: decimal - }); - } -} \ No newline at end of file diff --git a/assets/eip-5252/contracts/governance/Vote.sol b/assets/eip-5252/contracts/governance/Vote.sol deleted file mode 100644 index 003ebb1..0000000 --- a/assets/eip-5252/contracts/governance/Vote.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.2; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; -import "@openzeppelin/contracts/governance/Governor.sol"; -import "../interfaces/IInfluencer.sol"; -import "../interfaces/IABT.sol"; - -contract MyToken is ERC20, ERC20Permit, ERC20Votes, Governor { - constructor() ERC20("GovToken", "GOV") ERC20Permit("Governance Token") {} - - mapping(address => uint256[]) private _multiplier; - address public influencer; - - // The functions below are overrides required by Solidity. - - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal override(ERC20, ERC20Votes) { - super._afterTokenTransfer(from, to, amount); - } - - function _mint(address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._mint(to, amount); - } - - function _burn(address account, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._burn(account, amount); - } - - function _sqrt(uint256 x) internal returns (uint256 y) { - uint256 z = (x + 1) / 2; - y = x; - while (z < y) { - y = z; - z = (x / z + z) / 2; - } - } - - function getVotes(address account) - public - view - virtual - override - returns (uint256) - { - uint256 pos = _checkpoints[account].length; - uint256 vote = pos == 0 ? 0 : _checkpoints[account][pos - 1].votes; - // 0 as None, Multiplied with - uint256 multiplied = _multiplier[pos - 1] > 0 - ? _sqrt(vote) - : _sqrt(vote * _multiplier[pos - 1]); - return multiplied; - } - - function mulInfluence(address abt, uint256 id) public { - require(IABT(abt).ownerOf(id) == msg.sender, "Vote: not abt owner"); - uint256 pos = _checkpoints[msg.sender].length; - _multiplier[pos - 1] = IInfluencer.getInfluence(abt, id); - } - - function setInfluencer(address influencer_) public onlyGovernance { - influencer = influencer_; - } -} diff --git a/assets/eip-5252/contracts/interfaces/IABT.sol b/assets/eip-5252/contracts/interfaces/IABT.sol deleted file mode 100644 index e6b3d5f..0000000 --- a/assets/eip-5252/contracts/interfaces/IABT.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IABT { - function mint(address to) external; - function burn(uint256 tokenId_) external; - function exists(uint256 tokenId_) external view returns (bool); - function ownerOf(uint256 tokenId) external view returns (address owner); - function factory() external view returns (address factory); -} diff --git a/assets/eip-5252/contracts/interfaces/IDescriptor.sol b/assets/eip-5252/contracts/interfaces/IDescriptor.sol deleted file mode 100644 index eecf268..0000000 --- a/assets/eip-5252/contracts/interfaces/IDescriptor.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity ^0.8.0; - -interface IDescriptor { - function tokenURI(uint256 tokenId) external view returns (string memory); -} diff --git a/assets/eip-5252/contracts/interfaces/IERC20Minimal.sol b/assets/eip-5252/contracts/interfaces/IERC20Minimal.sol deleted file mode 100644 index 5e58531..0000000 --- a/assets/eip-5252/contracts/interfaces/IERC20Minimal.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.5.0; - -interface IERC20Minimal { - function totalSupply() external view returns (uint); - function balanceOf(address owner) external view returns (uint); - function decimals() external view returns (uint8); -} diff --git a/assets/eip-5252/contracts/interfaces/IFactory.sol b/assets/eip-5252/contracts/interfaces/IFactory.sol deleted file mode 100644 index e764bc1..0000000 --- a/assets/eip-5252/contracts/interfaces/IFactory.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IFactory { - - /// View funcs - /// NFT token address - function abt() external view returns (address); - /// Address of wrapped eth - function WETH() external view returns (address); - /// Address of a manager - function manager() external view returns (address); - - /// Getters - /// Get Config of CDP - function financeCodeHash() external pure returns (bytes32); - function createFinance(address weth, uint256 amount_, address recipient) external returns (address vault, uint256 id); - function getFinance(uint financeId_) external view returns (address); - - /// Event - event FinanceCreated(uint256 vaultId, address collateral, address debt, address creator, address vault, uint256 cAmount, uint256 dAmount); -} diff --git a/assets/eip-5252/contracts/interfaces/IFinance.sol b/assets/eip-5252/contracts/interfaces/IFinance.sol deleted file mode 100644 index 5f9749c..0000000 --- a/assets/eip-5252/contracts/interfaces/IFinance.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IFinance { - event DepositFundNative(uint256 vaultID, uint256 amount); - event WithdrawFundNative(uint256 vaultID, uint256 amount); - /// Getters - /// Address of a factory - function factory() external view returns (address); - /// Address of a manager - function manager() external view returns (address); - function influencer() external view returns (address); - /// Address of account bound token - function abt() external view returns (address); - /// Finance global identifier - function financeId() external view returns (uint256); - /// Finance Last Updated Date - function lastUpdated() external view returns (uint256); - /// Finance creation date - function createdAt() external view returns (uint256); - /// address of wrapped eth - function WETH() external view returns (address); - /// deposit amount of finance account - function deposit() external view returns (uint256); - - /// Functions - function initialize( - address manager_, - uint256 financeId_, - address abt_, - uint256 amount_, - address weth_ - ) external; - -} diff --git a/assets/eip-5252/contracts/interfaces/IInfluencer.sol b/assets/eip-5252/contracts/interfaces/IInfluencer.sol deleted file mode 100644 index c732c48..0000000 --- a/assets/eip-5252/contracts/interfaces/IInfluencer.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.5.0; - -interface IInfluencer { - function isEnforcer(address sender) external; -} diff --git a/assets/eip-5252/contracts/interfaces/IManager.sol b/assets/eip-5252/contracts/interfaces/IManager.sol deleted file mode 100644 index 3f7bfa1..0000000 --- a/assets/eip-5252/contracts/interfaces/IManager.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.8.0; - -interface IManager { - function factory() external view returns (address); - function influencer() external view returns (address); - function getExampleConfig(address something) external view returns (uint); - event ConfigInitialized(address something, uint example); - event FinanceCreated(uint id, address weth, address sender, address finance, uint input); -} diff --git a/assets/eip-5252/contracts/interfaces/IWETH.sol b/assets/eip-5252/contracts/interfaces/IWETH.sol deleted file mode 100644 index 6cf78ea..0000000 --- a/assets/eip-5252/contracts/interfaces/IWETH.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.5.0; - -interface IWETH { - function deposit() external payable; - function transfer(address to, uint value) external returns (bool); - function withdraw(uint) external; -} diff --git a/assets/eip-5252/contracts/libraries/CloneFactory.sol b/assets/eip-5252/contracts/libraries/CloneFactory.sol deleted file mode 100644 index 911e6b5..0000000 --- a/assets/eip-5252/contracts/libraries/CloneFactory.sol +++ /dev/null @@ -1,82 +0,0 @@ -library CloneFactory { - function _createClone(address target) internal returns (address result) { - // convert address to 20 bytes - bytes20 targetBytes = bytes20(target); - - // actual code // - // 3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3 - - // creation code // - // copy runtime code into memory and return it - // 3d602d80600a3d3981f3 - - // runtime code // - // code to delegatecall to address - // 363d3d373d3d3d363d73 address 5af43d82803e903d91602b57fd5bf3 - - assembly { - /* - reads the 32 bytes of memory starting at pointer stored in 0x40 - - In solidity, the 0x40 slot in memory is special: it contains the "free memory pointer" - which points to the end of the currently allocated memory. - */ - let clone := mload(0x40) - // store 32 bytes to memory starting at "clone" - mstore( - clone, - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 - ) - - /* - | 20 bytes | - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 - ^ - pointer - */ - // store 32 bytes to memory starting at "clone" + 20 bytes - // 0x14 = 20 - mstore(add(clone, 0x14), targetBytes) - - /* - | 20 bytes | 20 bytes | - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe - ^ - pointer - */ - // store 32 bytes to memory starting at "clone" + 40 bytes - // 0x28 = 40 - mstore( - add(clone, 0x28), - 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 - ) - - /* - | 20 bytes | 20 bytes | 15 bytes | - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3 - */ - // create new contract - // send 0 Ether - // code starts at pointer stored in "clone" - // code size 0x37 (55 bytes) - result := create(0, clone, 0x37) - } - } - - function _isClone(address target, address query) internal view returns (bool result) { - bytes20 targetBytes = bytes20(target); - assembly { - let clone := mload(0x40) - mstore(clone, 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000) - mstore(add(clone, 0xa), targetBytes) - mstore(add(clone, 0x1e), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) - - let other := add(clone, 0x40) - extcodecopy(query, other, 0, 0x2d) - result := and( - eq(mload(clone), mload(other)), - eq(mload(add(clone, 0xd)), mload(add(other, 0xd))) - ) - } - } -} diff --git a/assets/eip-5252/contracts/libraries/Initializable.sol b/assets/eip-5252/contracts/libraries/Initializable.sol deleted file mode 100644 index 55a3978..0000000 --- a/assets/eip-5252/contracts/libraries/Initializable.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -contract Initializable { - bool private _initialized = false; - - modifier initializer() { - // solhint-disable-next-line reason-string - require(!_initialized); - _; - _initialized = true; - } - - function initialized() external view returns (bool) { - return _initialized; - } -} diff --git a/assets/eip-5252/contracts/libraries/SVG.sol b/assets/eip-5252/contracts/libraries/SVG.sol deleted file mode 100644 index e69de29..0000000 diff --git a/assets/eip-5252/contracts/libraries/TransferHelper.sol b/assets/eip-5252/contracts/libraries/TransferHelper.sol deleted file mode 100644 index 550db79..0000000 --- a/assets/eip-5252/contracts/libraries/TransferHelper.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false -library TransferHelper { - function safeApprove(address token, address to, uint value) internal { - // bytes4(keccak256(bytes("approve(address,uint256)"))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "AF"); - } - - function safeTransfer(address token, address to, uint value) internal { - // bytes4(keccak256(bytes("transfer(address,uint256)"))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "TF"); - } - - function safeTransferFrom(address token, address from, address to, uint value) internal { - // bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "TFF"); - } - - function safeTransferETH(address to, uint value) internal { - (bool success,) = to.call{value:value}(new bytes(0)); - require(success, "ETF"); - } -} diff --git a/assets/eip-5252/hardhat.config.ts b/assets/eip-5252/hardhat.config.ts deleted file mode 100644 index 414e974..0000000 --- a/assets/eip-5252/hardhat.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; - -const config: HardhatUserConfig = { - solidity: "0.8.9", -}; - -export default config; diff --git a/assets/eip-5252/media/media.svg b/assets/eip-5252/media/media.svg deleted file mode 100644 index c74c943..0000000 --- a/assets/eip-5252/media/media.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/eip-5252/package.json b/assets/eip-5252/package.json deleted file mode 100644 index af7ea0c..0000000 --- a/assets/eip-5252/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "hardhat-project", - "devDependencies": { - "@nomicfoundation/hardhat-toolbox": "^1.0.2", - "hardhat": "^2.10.1", - "ts-node": "^10.9.1", - "typescript": "^4.7.4" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.7.1" - } -} diff --git a/assets/eip-5252/scripts/deploy.ts b/assets/eip-5252/scripts/deploy.ts deleted file mode 100644 index 90e8908..0000000 --- a/assets/eip-5252/scripts/deploy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ethers } from "hardhat"; - -async function main() { - const currentTimestampInSeconds = Math.round(Date.now() / 1000); - const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; - const unlockTime = currentTimestampInSeconds + ONE_YEAR_IN_SECS; - - const lockedAmount = ethers.utils.parseEther("1"); - - const Lock = await ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); - - await lock.deployed(); - - console.log("Lock with 1 ETH deployed to:", lock.address); -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/assets/eip-5252/test/Lock.ts b/assets/eip-5252/test/Lock.ts deleted file mode 100644 index 3127221..0000000 --- a/assets/eip-5252/test/Lock.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("Lock", function () { - // We define a fixture to reuse the same setup in every test. - // We use loadFixture to run this setup once, snapshot that state, - // and reset Hardhat Network to that snapshopt in every test. - async function deployOneYearLockFixture() { - const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; - const ONE_GWEI = 1_000_000_000; - - const lockedAmount = ONE_GWEI; - const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; - - // Contracts are deployed using the first signer/account by default - const [owner, otherAccount] = await ethers.getSigners(); - - const Lock = await ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); - - return { lock, unlockTime, lockedAmount, owner, otherAccount }; - } - - describe("Deployment", function () { - it("Should set the right unlockTime", async function () { - const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); - - expect(await lock.unlockTime()).to.equal(unlockTime); - }); - - it("Should set the right owner", async function () { - const { lock, owner } = await loadFixture(deployOneYearLockFixture); - - expect(await lock.owner()).to.equal(owner.address); - }); - - it("Should receive and store the funds to lock", async function () { - const { lock, lockedAmount } = await loadFixture( - deployOneYearLockFixture - ); - - expect(await ethers.provider.getBalance(lock.address)).to.equal( - lockedAmount - ); - }); - - it("Should fail if the unlockTime is not in the future", async function () { - // We don't use the fixture here because we want a different deployment - const latestTime = await time.latest(); - const Lock = await ethers.getContractFactory("Lock"); - await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( - "Unlock time should be in the future" - ); - }); - }); - - describe("Withdrawals", function () { - describe("Validations", function () { - it("Should revert with the right error if called too soon", async function () { - const { lock } = await loadFixture(deployOneYearLockFixture); - - await expect(lock.withdraw()).to.be.revertedWith( - "You can't withdraw yet" - ); - }); - - it("Should revert with the right error if called from another account", async function () { - const { lock, unlockTime, otherAccount } = await loadFixture( - deployOneYearLockFixture - ); - - // We can increase the time in Hardhat Network - await time.increaseTo(unlockTime); - - // We use lock.connect() to send a transaction from another account - await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( - "You aren't the owner" - ); - }); - - it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { - const { lock, unlockTime } = await loadFixture( - deployOneYearLockFixture - ); - - // Transactions are sent using the first signer by default - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()).not.to.be.reverted; - }); - }); - - describe("Events", function () { - it("Should emit an event on withdrawals", async function () { - const { lock, unlockTime, lockedAmount } = await loadFixture( - deployOneYearLockFixture - ); - - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()) - .to.emit(lock, "Withdrawal") - .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg - }); - }); - - describe("Transfers", function () { - it("Should transfer the funds to the owner", async function () { - const { lock, unlockTime, lockedAmount, owner } = await loadFixture( - deployOneYearLockFixture - ); - - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()).to.changeEtherBalances( - [owner, lock], - [lockedAmount, -lockedAmount] - ); - }); - }); - }); -}); diff --git a/assets/eip-5252/tsconfig.json b/assets/eip-5252/tsconfig.json deleted file mode 100644 index e5f1a64..0000000 --- a/assets/eip-5252/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true - } -} diff --git a/assets/eip-5269/contracts/ERC5269.sol b/assets/eip-5269/contracts/ERC5269.sol deleted file mode 100644 index 4d89b47..0000000 --- a/assets/eip-5269/contracts/ERC5269.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code - -pragma solidity ^0.8.9; - -import "./IERC5269.sol"; - -contract ERC5269 is IERC5269 { - bytes32 constant public EIP_STATUS = keccak256("DRAFTv1"); - constructor () { - emit OnSupportEIP(address(0x0), 5269, bytes32(0), EIP_STATUS, ""); - } - - function _supportEIP( - address /*caller*/, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata /*extraData*/) - internal virtual view returns (bytes32 eipStatus) { - if (majorEIPIdentifier == 5269) { - if (minorEIPIdentifier == bytes32(0)) { - return EIP_STATUS; - } - } - return bytes32(0); - } - - function supportEIP( - address caller, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata extraData) - external virtual view returns (bytes32 eipStatus) { - return _supportEIP(caller, majorEIPIdentifier, minorEIPIdentifier, extraData); - } -} diff --git a/assets/eip-5269/contracts/IERC5269.sol b/assets/eip-5269/contracts/IERC5269.sol deleted file mode 100644 index 8b44454..0000000 --- a/assets/eip-5269/contracts/IERC5269.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code -pragma solidity ^0.8.9; - -interface IERC5269 { - event OnSupportEIP( - address indexed caller, // when emitted with `address(0x0)` means all callers. - uint256 indexed majorEIPIdentifier, - bytes32 indexed minorEIPIdentifier, // 0 means the entire EIP - bytes32 eipStatus, - bytes extraData - ); - - /// @dev The core method of EIP/ERC Interface Detection - /// @param caller, a `address` value of the address of a caller being queried whether the given EIP is supported. - /// @param majorEIPIdentifier, a `uint256` value and SHOULD BE the EIP number being queried. Unless superseded by future EIP, such EIP number SHOULD BE less or equal to (0, 2^32-1]. For a function call to `supportEIP`, any value outside of this range is deemed unspecified and open to implementation's choice or for future EIPs to specify. - /// @param minorEIPIdentifier, a `bytes32` value reserved for authors of individual EIP to specify. For example the author of [EIP-721](/EIPS/eip-721) MAY specify `keccak256("ERC721Metadata")` or `keccak256("ERC721Metadata.tokenURI")` as `minorEIPIdentifier` to be quired for support. Author could also use this minorEIPIdentifier to specify different versions, such as EIP-712 has its V1-V4 with different behavior. - /// @param extraData, a `bytes` for [EIP-5750](/EIPS/eip-5750) for future extensions. - /// @return eipStatus a bytes32 indicating the status of EIP the contract supports. - /// - For FINAL EIPs, it MUST return `keccak256("FINAL")`. - /// - For non-FINAL EIPs, it SHOULD return `keccak256("DRAFT")`. - /// During EIP procedure, EIP authors are allowed to specify their own - /// eipStatus other than `FINAL` or `DRAFT` at their discretion such as `keccak256("DRAFTv1")` - /// or `keccak256("DRAFT-option1")`and such value of eipStatus MUST be documented in the EIP body - function supportEIP( - address caller, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata extraData) - external view returns (bytes32 eipStatus); -} diff --git a/assets/eip-5269/contracts/testing/ERC721ForTesting.sol b/assets/eip-5269/contracts/testing/ERC721ForTesting.sol deleted file mode 100644 index 22d21c4..0000000 --- a/assets/eip-5269/contracts/testing/ERC721ForTesting.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code -pragma solidity ^0.8.9; -// import 721 -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -// impport 5269 -import "../ERC5269.sol"; - -contract ERC721ForTesting is ERC721, ERC5269 { - - bytes32 constant public EIP_FINAL = keccak256("FINAL"); - constructor() ERC721("ERC721ForTesting", "E721FT") ERC5269() { - _mint(msg.sender, 0); - emit OnSupportEIP(address(0x0), 721, bytes32(0), EIP_FINAL, ""); - emit OnSupportEIP(address(0x0), 721, keccak256("ERC721Metadata"), EIP_FINAL, ""); - emit OnSupportEIP(address(0x0), 721, keccak256("ERC721Enumerable"), EIP_FINAL, ""); - } - - function supportEIP( - address caller, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata extraData) - external - override - view - returns (bytes32 eipStatus) { - if (majorEIPIdentifier == 721) { - if (minorEIPIdentifier == 0) { - return keccak256("FINAL"); - } else if (minorEIPIdentifier == keccak256("ERC721Metadata")) { - return keccak256("FINAL"); - } else if (minorEIPIdentifier == keccak256("ERC721Enumerable")) { - return keccak256("FINAL"); - } - } - return super._supportEIP(caller, majorEIPIdentifier, minorEIPIdentifier, extraData); - } -} diff --git a/assets/eip-5269/test/TestERC5269.ts b/assets/eip-5269/test/TestERC5269.ts deleted file mode 100644 index b71635d..0000000 --- a/assets/eip-5269/test/TestERC5269.ts +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code - -import { loadFixture, mine } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { BigNumber, ContractReceipt, Wallet } from "ethers"; -import { ethers } from "hardhat"; - -describe("ERC5269", function () { - async function deployFixture() { - // Contracts are deployed using the first signer/account by default - const [owner, mintSender, recipient] = await ethers.getSigners(); - const testWallet: Wallet = new ethers.Wallet("0x0000000000000000000000000000000000000000000000000000000000000001"); - - const factory = await ethers.getContractFactory("ERC5269"); - const contract = await factory.deploy(); - let tx1 = await contract.deployed(); - let txDeployErc5269: ContractReceipt = await tx1.deployTransaction.wait(); - - const ERC721ForTesting = await ethers.getContractFactory("ERC721ForTesting"); - const erc721ForTesting = await ERC721ForTesting.deploy(); - let tx2 = await erc721ForTesting.deployed(); - const txDeployErc721: ContractReceipt = await tx2.deployTransaction.wait(); - const provider = ethers.provider; - return { - provider, - contract, - erc721ForTesting, - tx1, txDeployErc5269, - tx2, txDeployErc721, - owner, mintSender, recipient, testWallet - }; - } - - describe("Deployment", function () { - it("Should be deployable", async function () { - await loadFixture(deployFixture); - }); - - it("Should emit proper OnSupportEIP events", async function () { - let { txDeployErc721 } = await loadFixture(deployFixture); - let events = txDeployErc721.events?.filter(event => event.event === 'OnSupportEIP'); - expect(events).to.have.lengthOf(4); - - let ev5269 = events!.filter( - (event) => event.args!.majorEIPIdentifier.eq(5269)); - expect(ev5269).to.have.lengthOf(1); - expect(ev5269[0].args!.caller).to.equal(BigNumber.from(0)); - expect(ev5269[0].args!.minorEIPIdentifier).to.equal(BigNumber.from(0)); - expect(ev5269[0].args!.eipStatus).to.equal(ethers.utils.id("DRAFTv1")); - - let ev721 = events!.filter( - (event) => event.args!.majorEIPIdentifier.eq(721)); - expect(ev721).to.have.lengthOf(3); - expect(ev721[0].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[0].args!.minorEIPIdentifier).to.equal(BigNumber.from(0)); - expect(ev721[0].args!.eipStatus).to.equal(ethers.utils.id("FINAL")); - - expect(ev721[1].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[1].args!.minorEIPIdentifier).to.equal(ethers.utils.id("ERC721Metadata")); - expect(ev721[1].args!.eipStatus).to.equal(ethers.utils.id("FINAL")); - - expect(ev721[2].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[2].args!.minorEIPIdentifier).to.equal(ethers.utils.id("ERC721Enumerable")); - expect(ev721[2].args!.eipStatus).to.equal(ethers.utils.id("FINAL")); - }); - - it("Should return proper eipStatus value when called supportEIP() for declared supported EIP/features", async function () { - let { erc721ForTesting, owner } = await loadFixture(deployFixture); - expect(await erc721ForTesting.supportEIP(owner.address, 5269, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("DRAFTv1")); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("FINAL")); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("ERC721Metadata"), [])).to.equal(ethers.utils.id("FINAL")); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("ERC721Enumerable"), [])).to.equal(ethers.utils.id("FINAL")); - - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0)); - expect(await erc721ForTesting.supportEIP(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0)); - }); - - it("Should return zero as eipStatus value when called supportEIP() for non declared EIP/features", async function () { - let { erc721ForTesting, owner } = await loadFixture(deployFixture); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0)); - expect(await erc721ForTesting.supportEIP(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0)); - }); - }); -}); diff --git a/assets/eip-5289/ERC5289Library.sol b/assets/eip-5289/ERC5289Library.sol deleted file mode 100644 index 04bb6c7..0000000 --- a/assets/eip-5289/ERC5289Library.sol +++ /dev/null @@ -1,42 +0,0 @@ -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interfaces/IERC165.sol"; -import "./interfaces/IERC5289Library.sol"; - -contract ERC5289Library is IERC165, IERC5289Library { - uint16 private counter = 0; - mapping(uint16 => string) private uris; - mapping(uint16 => mapping(address => uint64)) signedAt; - - constructor() { } - - function registerDocument(string memory uri) public returns (uint16) { - uris[counter] = uri; - return counter++; - } - - function legalDocument(uint16 documentId) public view returns (string uri) { - return uris[documentId]; - } - - function documentSigned(address user, uint16 documentId) public view returns (bool isSigned) { - return signedAt[documentId][user] != 0; - } - - function documentSignedAt(address user, uint16 documentId) public view returns (uint64 timestamp) { - return signedAt[documentId][user]; - } - - function signDocument(address signer, uint16 documentId) public { - require(signer == msg.sender, "invalid user"); - - signedAt[documentId][msg.sender] = uint64(block.timestamp); - - emit DocumentSigned(msg.sender, documentId); - } - - function supportsInterface(bytes4 _interfaceId) public view returns (bool) { - return _interfaceId == type(IERC5289Library).interfaceId; - } -} diff --git a/assets/eip-5289/example-popup.png b/assets/eip-5289/example-popup.png deleted file mode 100644 index 957bb6b..0000000 Binary files a/assets/eip-5289/example-popup.png and /dev/null differ diff --git a/assets/eip-5289/interfaces/IERC165.sol b/assets/eip-5289/interfaces/IERC165.sol deleted file mode 100644 index 3e6ec8e..0000000 --- a/assets/eip-5289/interfaces/IERC165.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} diff --git a/assets/eip-5289/interfaces/IERC5289Library.sol b/assets/eip-5289/interfaces/IERC5289Library.sol deleted file mode 100644 index 6eb68b1..0000000 --- a/assets/eip-5289/interfaces/IERC5289Library.sol +++ /dev/null @@ -1,23 +0,0 @@ -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC165.sol"; - -interface IERC5289Library is IERC165 { - /// @notice Emitted when signDocument is called - event DocumentSigned(address indexed signer, uint16 indexed documentId); - - /// @notice An immutable link to the legal document (RECOMMENDED to be hosted on IPFS). This MUST use a common file format, such as PDF, HTML, TeX, or Markdown. - function legalDocument(uint16 documentId) external view returns (string memory); - - /// @notice Returns whether or not the given user signed the document. - function documentSigned(address user, uint16 documentId) external view returns (bool signed); - - /// @notice Returns when the the given user signed the document. - /// @dev If the user has not signed the document, the timestamp may be anything. - function documentSignedAt(address user, uint16 documentId) external view returns (uint64 timestamp); - - /// @notice Sign a document - /// @dev This MUST be validated by the smart contract. This MUST emit DocumentSigned or throw. - function signDocument(address signer, uint16 documentId) external; -} diff --git a/assets/eip-5334/ERC5334.sol b/assets/eip-5334/ERC5334.sol deleted file mode 100644 index b6a79ac..0000000 --- a/assets/eip-5334/ERC5334.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5334.sol"; - -contract ERC5334 is ERC721, IERC5334 { - struct UserInfo - { - address user; // address of user role - uint64 expires; // unix timestamp, user expires - uint8 level; // user level - } - - mapping (uint256 => UserInfo) internal _users; - - constructor(string memory name_, string memory symbol_) - ERC721(name_,symbol_) - { - } - - /// @notice set the user and expires and level of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - /// @param level user level - function setUser(uint256 tokenId, address user, uint64 expires, uint8 level) public virtual{ - require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: transfer caller is not owner nor approved"); - UserInfo storage info = _users[tokenId]; - info.user = user; - info.expires = expires; - info.level = level - emit UpdateUser(tokenId,user,expires,level); - } - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId)public view virtual returns(address){ - if( uint256(_users[tokenId].expires) >= block.timestamp){ - return _users[tokenId].user; - } - else{ - return address(0); - } - } - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) public view virtual returns(uint256){ - return _users[tokenId].expires; - } - - /// @notice Get the user level of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user level for - /// @return The user level for this NFT - function userLevel(uint256 tokenId) public view virtual returns(uint256){ - return _users[tokenId].level; - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC5334).interfaceId || super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId); - - if (from != to && _users[tokenId].user != address(0)) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0, 0); - } - } -} - diff --git a/assets/eip-5334/IERC5334.sol b/assets/eip-5334/IERC5334.sol deleted file mode 100644 index 0adc386..0000000 --- a/assets/eip-5334/IERC5334.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5334 { - // Logged when the user of a token assigns a new user or updates expires - /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed or the `level` of the `user` is changed - /// The zero address for user indicates that there is no user address - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires, uint8 level); - - /// @notice set the user and expires of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - /// @param level user level - function setUser(uint256 tokenId, address user, uint64 expires, uint8 level) external ; - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId) external view returns(address); - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) external view returns(uint256); - - /// @notice Get the user level of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user level for - /// @return The user level for this NFT - function userLevel(uint256 tokenId) external view returns(uint256); -} diff --git a/assets/eip-5345/walletconnect-flow.png b/assets/eip-5345/walletconnect-flow.png deleted file mode 100644 index 4e0ab78..0000000 Binary files a/assets/eip-5345/walletconnect-flow.png and /dev/null differ diff --git a/assets/eip-5453/AERC5453.sol b/assets/eip-5453/AERC5453.sol deleted file mode 100644 index f3b4d76..0000000 --- a/assets/eip-5453/AERC5453.sol +++ /dev/null @@ -1,271 +0,0 @@ -// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; - -import "./IERC5453.sol"; - -abstract contract AERC5453Endorsible is EIP712, - IERC5453EndorsementCore, IERC5453EndorsementDigest, IERC5453EndorsementDataTypeA, IERC5453EndorsementDataTypeB { - uint256 private threshold; - uint256 private currentNonce = 0; - bytes32 constant MAGIC_WORLD = keccak256("ERC5453-ENDORSEMENT"); // ASCII of "ENDORSED" - uint256 constant ERC5453_TYPE_A = 1; - uint256 constant ERC5453_TYPE_B = 2; - - constructor( - string memory _name, - string memory _erc721Version - ) EIP712(_name, _erc721Version) {} - - function _validate( - bytes32 msgDigest, - SingleEndorsementData memory endersement - ) internal virtual { - require( - endersement.sig.length == 65, - "AERC5453Endorsible: wrong signature length" - ); - require( - SignatureChecker.isValidSignatureNow( - endersement.endorserAddress, - msgDigest, - endersement.sig - ), - "AERC5453Endorsible: invalid signature" - ); - } - - function _extractEndorsers( - bytes32 digest, - GeneralExtensionDataStruct memory data - ) internal virtual returns (address[] memory endorsers) { - require( - data.erc5453MagicWord == MAGIC_WORLD, - "AERC5453Endorsible: MagicWord not matched" - ); - require( - data.validSince <= block.number, - "AERC5453Endorsible: Not valid yet" - ); // TODO consider per-Endorser validSince - require(data.validBy >= block.number, "AERC5453Endorsible: Expired"); // TODO consider per-Endorser validBy - require( - currentNonce == data.nonce, - "AERC5453Endorsible: Nonce not matched" - ); // TODO consider per-Endorser nonce or range of nonce - currentNonce += 1; - - if (data.erc5453Type == ERC5453_TYPE_A) { - SingleEndorsementData memory endersement = abi.decode( - data.endorsementPayload, - (SingleEndorsementData) - ); - endorsers = new address[](1); - endorsers[0] = endersement.endorserAddress; - _validate(digest, endersement); - } else if (data.erc5453Type == ERC5453_TYPE_B) { - SingleEndorsementData[] memory endorsements = abi.decode( - data.endorsementPayload, - (SingleEndorsementData[]) - ); - endorsers = new address[](endorsements.length); - for (uint256 i = 0; i < endorsements.length; ++i) { - endorsers[i] = endorsements[i].endorserAddress; - _validate(digest, endorsements[i]); - } - return endorsers; - } - } - - function _decodeExtensionData( - bytes memory extensionData - ) internal pure virtual returns (GeneralExtensionDataStruct memory) { - return abi.decode(extensionData, (GeneralExtensionDataStruct)); - } - - // Well, I know this is epensive. Let's improve it later. - function _noRepeat(address[] memory _owners) internal pure returns (bool) { - for (uint256 i = 0; i < _owners.length; i++) { - for (uint256 j = i + 1; j < _owners.length; j++) { - if (_owners[i] == _owners[j]) { - return false; - } - } - } - return true; - } - - function _isEndorsed( - bytes32 _functionParamStructHash, - bytes calldata _extraData - ) internal returns (bool) { - GeneralExtensionDataStruct memory _data = _decodeExtensionData( - _extraData - ); - bytes32 finalDigest = _computeValidityDigest( - _functionParamStructHash, - _data.validSince, - _data.validBy, - _data.nonce - ); - - address[] memory endorsers = _extractEndorsers(finalDigest, _data); - require( - endorsers.length >= threshold, - "AERC5453Endorsable: not enough endorsers" - ); - require(_noRepeat(endorsers)); - for (uint256 i = 0; i < endorsers.length; i++) { - require( - _isEligibleEndorser(endorsers[i]), - "AERC5453Endorsable: not eligible endorsers" - ); // everyone must be a legit endorser - } - return true; - } - - function _isEligibleEndorser( - address /*_endorser*/ - ) internal view virtual returns (bool); - - modifier onlyEndorsed( - bytes32 _functionParamStructHash, - bytes calldata _extensionData - ) { - require(_isEndorsed(_functionParamStructHash, _extensionData)); - _; - } - - function _computeValidityDigest( - bytes32 _functionParamStructHash, - uint256 _validSince, - uint256 _validBy, - uint256 _nonce - ) internal view returns (bytes32) { - return - super._hashTypedDataV4( - keccak256( - abi.encode( - keccak256( - "ValidityBound(bytes32 functionParamStructHash,uint256 validSince,uint256 validBy,uint256 nonce)" - ), - _functionParamStructHash, - _validSince, - _validBy, - _nonce - ) - ) - ); - } - - function _computeFunctionParamHash( - string memory _functionStructure, - bytes memory _functionParamPacked - ) internal pure returns (bytes32) { - bytes32 functionParamStructHash = keccak256( - abi.encodePacked( - keccak256(bytes(_functionStructure)), - _functionParamPacked - ) - ); - return functionParamStructHash; - } - - function _setThreshold(uint256 _threshold) internal virtual { - threshold = _threshold; - } - - function computeValidityDigest( - bytes32 _functionParamStructHash, - uint256 _validSince, - uint256 _validBy, - uint256 _nonce - ) external view override returns (bytes32) { - return - _computeValidityDigest( - _functionParamStructHash, - _validSince, - _validBy, - _nonce - ); - } - - function computeFunctionParamHash( - string memory _functionName, - bytes memory _functionParamPacked - ) external pure override returns (bytes32) { - return - _computeFunctionParamHash( - _functionName, - _functionParamPacked - ); - } - - function eip5453Nonce(address addr) external view override returns (uint256) { - require(address(this) == addr, "AERC5453Endorsable: not self"); - return currentNonce; - } - - function isEligibleEndorser(address _endorser) - external - view - override - returns (bool) - { - return _isEligibleEndorser(_endorser); - } - - function computeExtensionDataTypeA( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address endorserAddress, - bytes calldata sig - ) external pure override returns (bytes memory) { - return - abi.encode( - GeneralExtensionDataStruct( - MAGIC_WORLD, - ERC5453_TYPE_A, - nonce, - validSince, - validBy, - abi.encode(SingleEndorsementData(endorserAddress, sig)) - ) - ); - } - - function computeExtensionDataTypeB( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address[] calldata endorserAddress, - bytes[] calldata sigs - ) external pure override returns (bytes memory) { - require(endorserAddress.length == sigs.length); - SingleEndorsementData[] - memory endorsements = new SingleEndorsementData[]( - endorserAddress.length - ); - for (uint256 i = 0; i < endorserAddress.length; ++i) { - endorsements[i] = SingleEndorsementData( - endorserAddress[i], - sigs[i] - ); - } - return - abi.encode( - GeneralExtensionDataStruct( - MAGIC_WORLD, - ERC5453_TYPE_B, - nonce, - validSince, - validBy, - abi.encode(endorsements) - ) - ); - } -} diff --git a/assets/eip-5453/EndorsableERC721.sol b/assets/eip-5453/EndorsableERC721.sol deleted file mode 100644 index 3299ef8..0000000 --- a/assets/eip-5453/EndorsableERC721.sol +++ /dev/null @@ -1,47 +0,0 @@ -/// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -import "./AERC5453.sol"; - -contract EndorsableERC721 is ERC721, AERC5453Endorsible { - mapping(address => bool) private owners; - - constructor() - ERC721("ERC721ForTesting", "ERC721ForTesting") - AERC5453Endorsible("EndorsableERC721", "v1") - { - owners[msg.sender] = true; - } - - function addOwner(address _owner) external { - require(owners[msg.sender], "EndorsableERC721: not owner"); - owners[_owner] = true; - } - - function mint( - address _to, - uint256 _tokenId, - bytes calldata _extraData - ) - external - onlyEndorsed( - _computeFunctionParamHash( - "function mint(address _to,uint256 _tokenId)", - abi.encode(_to, _tokenId) - ), - _extraData - ) - { - _mint(_to, _tokenId); - } - - function _isEligibleEndorser( - address _endorser - ) internal view override returns (bool) { - return owners[_endorser]; - } -} diff --git a/assets/eip-5453/IERC5453.sol b/assets/eip-5453/IERC5453.sol deleted file mode 100644 index d51df70..0000000 --- a/assets/eip-5453/IERC5453.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -struct ValidityBound { - bytes32 functionParamStructHash; - uint256 validSince; - uint256 validBy; - uint256 nonce; -} - -struct SingleEndorsementData { - address endorserAddress; // 32 - bytes sig; // dynamic = 65 -} - -struct GeneralExtensionDataStruct { - bytes32 erc5453MagicWord; - uint256 erc5453Type; - uint256 nonce; - uint256 validSince; - uint256 validBy; - bytes endorsementPayload; -} - -interface IERC5453EndorsementCore { - function eip5453Nonce(address endorser) external view returns (uint256); - function isEligibleEndorser(address endorser) external view returns (bool); -} - -interface IERC5453EndorsementDigest { - function computeValidityDigest( - bytes32 _functionParamStructHash, - uint256 _validSince, - uint256 _validBy, - uint256 _nonce - ) external view returns (bytes32); - - function computeFunctionParamHash( - string memory _functionName, - bytes memory _functionParamPacked - ) external view returns (bytes32); -} - -interface IERC5453EndorsementDataTypeA { - function computeExtensionDataTypeA( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address endorserAddress, - bytes calldata sig - ) external view returns (bytes memory); -} - - -interface IERC5453EndorsementDataTypeB { - function computeExtensionDataTypeB( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address[] calldata endorserAddress, - bytes[] calldata sigs - ) external view returns (bytes memory); -} diff --git a/assets/eip-5453/ThresholdMultiSigForwarder.sol b/assets/eip-5453/ThresholdMultiSigForwarder.sol deleted file mode 100644 index c366e6d..0000000 --- a/assets/eip-5453/ThresholdMultiSigForwarder.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -import "./AERC5453.sol"; - -contract ThresholdMultiSigForwarder is AERC5453Endorsible { - mapping(address => bool) private owners; - uint256 private ownerCount; - - constructor() AERC5453Endorsible("ThresholdMultiSigForwarder", "v1") {} - - function initialize( - address[] calldata _owners, - uint256 _threshold - ) external { - require(_threshold >= 1, "Threshold must be positive"); - require(_owners.length >= _threshold); - require(_noRepeat(_owners)); - _setThreshold(_threshold); - for (uint256 i = 0; i < _owners.length; i++) { - owners[_owners[i]] = true; - } - ownerCount = _owners.length; - } - - function forward( - address _dest, - uint256 _value, - uint256 _gasLimit, - bytes calldata _calldata, - bytes calldata _extraData - ) - external - onlyEndorsed( - _computeFunctionParamHash( - "function forward(address _dest,uint256 _value,uint256 _gasLimit,bytes calldata _calldata)", - abi.encode(_dest, _value, _gasLimit, keccak256(_calldata)) - ), - _extraData - ) - { - string memory errorMessage = "Fail to call remote contract"; - (bool success, bytes memory returndata) = _dest.call{value: _value}( - _calldata - ); - Address.verifyCallResult(success, returndata, errorMessage); - } - - function _isEligibleEndorser( - address _endorser - ) internal view override returns (bool) { - return owners[_endorser] == true; - } -} diff --git a/assets/eip-5489/contracts/ERC5489.sol b/assets/eip-5489/contracts/ERC5489.sol deleted file mode 100644 index 8625d77..0000000 --- a/assets/eip-5489/contracts/ERC5489.sol +++ /dev/null @@ -1,127 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC5489.sol"; - -contract ERC5489 is IERC5489, ERC721Enumerable, Ownable { - using EnumerableSet for EnumerableSet.AddressSet; - - mapping(uint256 => EnumerableSet.AddressSet) tokenId2AuthroizedAddresses; - mapping(uint256 => mapping(address=> string)) tokenId2Address2Value; - mapping(uint256 => string) tokenId2ImageUri; - - string private _imageURI; - string private _name; - - constructor() ERC721("Hyperlink NFT Collection", "HNFT") {} - - modifier onlyTokenOwner(uint256 tokenId) { - require(_msgSender() == ownerOf(tokenId), "should be the token owner"); - _; - } - - modifier onlySlotManager(uint256 tokenId) { - require(_msgSender() == ownerOf(tokenId) || tokenId2AuthroizedAddresses[tokenId].contains(_msgSender()), "address should be authorized"); - _; - } - - function setSlotUri(uint256 tokenId, string calldata value) override external onlySlotManager(tokenId) { - tokenId2Address2Value[tokenId][_msgSender()] = value; - - emit SlotUriUpdated(tokenId, _msgSender(), value); - } - - function getSlotUri(uint256 tokenId, address slotManagerAddr) override external view returns (string memory) { - return tokenId2Address2Value[tokenId][slotManagerAddr]; - } - - function authorizeSlotTo(uint256 tokenId, address slotManagerAddr) override external onlyTokenOwner(tokenId) { - require(!tokenId2AuthroizedAddresses[tokenId].contains(slotManagerAddr), "address already authorized"); - - _authorizeSlotTo(tokenId, slotManagerAddr); - } - - function _authorizeSlotTo(uint256 tokenId, address slotManagerAddr) private { - tokenId2AuthroizedAddresses[tokenId].add(slotManagerAddr); - emit SlotAuthorizationCreated(tokenId, slotManagerAddr); - } - - function revokeAuthorization(uint256 tokenId, address slotManagerAddr) override external onlyTokenOwner(tokenId) { - tokenId2AuthroizedAddresses[tokenId].remove(slotManagerAddr); - delete tokenId2Address2Value[tokenId][slotManagerAddr]; - - emit SlotAuthorizationRevoked(tokenId, slotManagerAddr); - } - - function revokeAllAuthorizations(uint256 tokenId) override external onlyTokenOwner(tokenId) { - for (uint256 i = tokenId2AuthroizedAddresses[tokenId].length() - 1;i > 0; i--) { - address addr = tokenId2AuthroizedAddresses[tokenId].at(i); - tokenId2AuthroizedAddresses[tokenId].remove(addr); - delete tokenId2Address2Value[tokenId][addr]; - - emit SlotAuthorizationRevoked(tokenId, addr); - } - - if (tokenId2AuthroizedAddresses[tokenId].length() > 0) { - address addr = tokenId2AuthroizedAddresses[tokenId].at(0); - tokenId2AuthroizedAddresses[tokenId].remove(addr); - delete tokenId2Address2Value[tokenId][addr]; - - emit SlotAuthorizationRevoked(tokenId, addr); - } - } - - function isSlotManager(uint256 tokenId, address addr) public view returns (bool) { - return tokenId2AuthroizedAddresses[tokenId].contains(addr); - } - - // !!expensive, should call only when no gas is needed; - function getSlotManagers(uint256 tokenId) external view returns (address[] memory) { - return tokenId2AuthroizedAddresses[tokenId].values(); - } - - function _mintToken(uint256 tokenId, string calldata imageUri) private { - _safeMint(msg.sender, tokenId); - tokenId2ImageUri[tokenId] = imageUri; - } - - function mint(string calldata imageUri) external { - uint256 tokenId = totalSupply() + 1; - _mintToken(tokenId, imageUri); - } - - function mintAndAuthorizeTo(string calldata imageUri, address slotManagerAddr) external { - uint256 tokenId = totalSupply() + 1; - _mintToken(tokenId, imageUri); - _authorizeSlotTo(tokenId, slotManagerAddr); - } - - function tokenURI(uint256 _tokenId) public view override returns (string memory) { - require( - _exists(_tokenId), - "URI query for nonexistent token" - ); - - return - string( - abi.encodePacked( - "data:application/json;base64,", - Base64.encode( - bytes( - abi.encodePacked( - '{"name":"', - abi.encodePacked( - _name, - " # ", - Strings.toString(_tokenId) - ), - '",', - '"description":"Hyperlink NFT collection created with Parami Foundation"', - '}' - ) - ) - ) - ) - ); - } -} diff --git a/assets/eip-5489/contracts/IERC5489.sol b/assets/eip-5489/contracts/IERC5489.sol deleted file mode 100644 index ba6ba4f..0000000 --- a/assets/eip-5489/contracts/IERC5489.sol +++ /dev/null @@ -1,63 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5489 { - /** - * @dev this event emits when the slot on `tokenId` is authorzized to `slotManagerAddr` - */ - event SlotAuthorizationCreated(uint256 indexed tokenId, address indexed slotManagerAddr); - - /** - * @dev this event emits when the authorization on slot `slotManagerAddr` of token `tokenId` is revoked. - * So, the corresponding DApp can handle this to stop on-going incentives or rights - */ - event SlotAuthorizationRevoked(uint256 indexed tokenId, address indexed slotManagerAddr); - - /** - * @dev this event emits when the uri on slot `slotManagerAddr` of token `tokenId` has been updated to `uri`. - */ - event SlotUriUpdated(uint256 indexed tokenId, address indexed slotManagerAddr, string uri); - - /** - * @dev - * Authorize a hyperlink slot on `tokenId` to address `slotManagerAddr`. - * Indeed slot is an entry in a map whose key is address `slotManagerAddr`. - * Only the address `slotManagerAddr` can manage the specific slot. - * This method will emit SlotAuthorizationCreated event - */ - function authorizeSlotTo(uint256 tokenId, address slotManagerAddr) external; - - /** - * @dev - * Revoke the authorization of the slot indicated by `slotManagerAddr` on token `tokenId` - * This method will emit SlotAuthorizationRevoked event - */ - function revokeAuthorization(uint256 tokenId, address slotManagerAddr) external; - - /** - * @dev - * Revoke all authorizations of slot on token `tokenId` - * This method will emit SlotAuthorizationRevoked event for each slot - */ - function revokeAllAuthorizations(uint256 tokenId) external; - - /** - * @dev - * Set uri for a slot on a token, which is indicated by `tokenId` and `slotManagerAddr` - * Only the address with authorization through {authorizeSlotTo} can manipulate this slot. - * This method will emit SlotUriUpdated event - */ - function setSlotUri( - uint256 tokenId, - string calldata newUri - ) external; - - /** - * @dev - * returns the latest uri of an slot on a token, which is indicated by `tokenId`, `slotManagerAddr` - */ - function getSlotUri(uint256 tokenId, address slotManagerAddr) - external - view - returns (string memory); -} \ No newline at end of file diff --git a/assets/eip-5496/contracts/ERC5496.sol b/assets/eip-5496/contracts/ERC5496.sol deleted file mode 100644 index fe5c467..0000000 --- a/assets/eip-5496/contracts/ERC5496.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "./IERC5496.sol"; - -contract ERC5496 is ERC721, IERC5496 { - struct PrivilegeRecord { - address user; - uint256 expiresAt; - } - struct PrivilegeStorage { - uint lastExpiresAt; - // privId => PrivilegeRecord - mapping(uint => PrivilegeRecord) privilegeEntry; - } - - uint public privilegeTotal; - // tokenId => PrivilegeStorage - mapping(uint => PrivilegeStorage) public privilegeBook; - mapping(address => mapping(address => bool)) private privilegeDelegator; - - constructor(string memory name_, string memory symbol_) - ERC721(name_,symbol_) - { - - } - - function setPrivilege( - uint tokenId, - uint privId, - address user, - uint64 expires - ) external virtual { - require((hasPrivilege(tokenId, privId, ownerOf(tokenId)) && _isApprovedOrOwner(msg.sender, tokenId)) || _isDelegatorOrHolder(msg.sender, tokenId, privId), "ERC721: transfer caller is not owner nor approved"); - require(expires < block.timestamp + 30 days, "expire time invalid"); - require(privId < privilegeTotal, "invalid privilege id"); - privilegeBook[tokenId].privilegeEntry[privId].user = user; - if (_isApprovedOrOwner(msg.sender, tokenId)) { - privilegeBook[tokenId].privilegeEntry[privId].expiresAt = expires; - if (privilegeBook[tokenId].lastExpiresAt < expires) { - privilegeBook[tokenId].lastExpiresAt = expires; - } - } - emit PrivilegeAssigned(tokenId, privId, user, uint64(privilegeBook[tokenId].privilegeEntry[privId].expiresAt)); - } - - function hasPrivilege( - uint256 tokenId, - uint256 privId, - address user - ) public virtual view returns(bool) { - if ( privilegeBook[tokenId].privilegeEntry[privId].expiresAt >= block.timestamp ){ - return privilegeBook[tokenId].privilegeEntry[privId].user == user; - } - return ownerOf(tokenId) == user; - } - - function privilegeExpires( - uint256 tokenId, - uint256 privId - ) public virtual view returns(uint256){ - return privilegeBook[tokenId].privilegeEntry[privId].expiresAt; - } - - function _setPrivilegeTotal( - uint total - ) internal { - emit PrivilegeTotalChanged(total, privilegeTotal); - privilegeTotal = total; - } - - function getPrivilegeInfo(uint tokenId, uint privId) external view returns(address user, uint256 expiresAt) { - return (privilegeBook[tokenId].privilegeEntry[privId].user, privilegeBook[tokenId].privilegeEntry[privId].expiresAt); - } - - function setDelegator(address delegator, bool enabled) external { - privilegeDelegator[msg.sender][delegator] = enabled; - } - - function _isDelegatorOrHolder(address delegator, uint256 tokenId, uint privId) internal virtual view returns (bool) { - address holder = privilegeBook[tokenId].privilegeEntry[privId].user; - return (delegator == holder || privilegeDelegator[holder][delegator]); - } - - function supportsInterface(bytes4 interfaceId) public override virtual view returns (bool) { - return interfaceId == type(IERC5496).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5496/contracts/ERC5496CloneableDemo.sol b/assets/eip-5496/contracts/ERC5496CloneableDemo.sol deleted file mode 100644 index 49f7fb1..0000000 --- a/assets/eip-5496/contracts/ERC5496CloneableDemo.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./extensions/ERC5496Cloneable.sol"; - -contract ERC5496CloneableDemo is ERC5496Cloneable { - - constructor(string memory name_, string memory symbol_) - ERC5496(name_,symbol_) - { - - } - - function mint(uint256 tokenId, address to) public { - _mint(to, tokenId); - } - - function setPrivilegeTotal(uint total) external { - _setPrivilegeTotal(total); - } - - function increasePrivileges(bool _cloneable) external { - uint privId = privilegeTotal; - _setPrivilegeTotal(privilegeTotal + 1); - cloneable[privId] = _cloneable; - } -} diff --git a/assets/eip-5496/contracts/ERC5496Demo.sol b/assets/eip-5496/contracts/ERC5496Demo.sol deleted file mode 100644 index deb7c07..0000000 --- a/assets/eip-5496/contracts/ERC5496Demo.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5496.sol"; - -contract ERC5496Demo is ERC5496 { - - constructor(string memory name_, string memory symbol_) - ERC5496(name_,symbol_) - { - - } - - function mint(uint256 tokenId, address to) public { - _mint(to, tokenId); - } - - function setPrivilegeTotal(uint total) external { - _setPrivilegeTotal(total); - } - - function increasePrivileges(bool ) external { - _setPrivilegeTotal(privilegeTotal + 1); - } -} diff --git a/assets/eip-5496/contracts/IERC5496.sol b/assets/eip-5496/contracts/IERC5496.sol deleted file mode 100644 index 178241a..0000000 --- a/assets/eip-5496/contracts/IERC5496.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5496 { - event PrivilegeAssigned(uint tokenId, uint privId, address user, uint64 expires); - event PrivilegeTotalChanged(uint newTotal, uint oldTotal); - function setPrivilege(uint256 tokenId, uint privId, address user, uint64 expires) external; - function privilegeExpires(uint256 tokenId, uint256 privId) external view returns(uint256); - function hasPrivilege(uint256 tokenId, uint256 privId, address user) external view returns(bool); -} diff --git a/assets/eip-5496/contracts/extensions/ERC5496Cloneable.sol b/assets/eip-5496/contracts/extensions/ERC5496Cloneable.sol deleted file mode 100644 index c08d829..0000000 --- a/assets/eip-5496/contracts/extensions/ERC5496Cloneable.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721Enumerable.sol) - -pragma solidity ^0.8.0; - -import "../ERC5496.sol"; -import "./IERC5496Cloneable.sol"; - -/** - * @dev This implements an optional extension of {ERC721} defined in the EIP that adds - * enumerability of all the token ids in the contract as well as all token ids owned by each - * account. - */ -abstract contract ERC5496Cloneable is ERC5496, IERC5496Cloneable { - struct CloneableRecord { - // account => shared - mapping(address => bool) shared; - // account => refer - mapping(address => address) referrer; - } - - // privId => isCloneable - mapping(uint => bool) public cloneable; - // tokenId => privId => CloneableRecord - mapping(uint => mapping(uint => CloneableRecord)) cloneableSetting; - - function supportsInterface(bytes4 interfaceId) public override virtual view returns (bool) { - return interfaceId == type(IERC5496Cloneable).interfaceId || super.supportsInterface(interfaceId); - } - - function hasPrivilege( - uint256 tokenId, - uint256 privId, - address user - ) public override virtual view returns(bool) { - if ( privilegeBook[tokenId].privilegeEntry[privId].expiresAt >= block.timestamp ){ - return cloneableSetting[tokenId][privId].shared[user] || super.hasPrivilege(tokenId, privId, user); - } - return ownerOf(tokenId) == user; - } - - function clonePrivilege(uint tokenId, uint privId, address referrer) external returns (bool) { - require(cloneable[privId], "privilege not cloneable"); - return _clonePrivilege(tokenId, privId, referrer); - } - - function _clonePrivilege(uint tokenId, uint privId, address referrer) internal returns (bool) { - require(privilegeBook[tokenId].privilegeEntry[privId].user == referrer || cloneableSetting[tokenId][privId].shared[referrer], "referrer not exists"); - if (cloneableSetting[tokenId][privId].referrer[msg.sender] == address(0)) { - cloneableSetting[tokenId][privId].shared[msg.sender] = true; - cloneableSetting[tokenId][privId].referrer[msg.sender] = referrer; - emit PrivilegeCloned(tokenId, privId, referrer, msg.sender); - return true; - } - return false; - } -} diff --git a/assets/eip-5496/contracts/extensions/IERC5496Cloneable.sol b/assets/eip-5496/contracts/extensions/IERC5496Cloneable.sol deleted file mode 100644 index 9228e7c..0000000 --- a/assets/eip-5496/contracts/extensions/IERC5496Cloneable.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5496Cloneable { - event PrivilegeCloned(uint tokenId, uint privId, address from, address to); - function clonePrivilege(uint tokenId, uint privId, address referrer) external returns (bool); -} diff --git a/assets/eip-5496/test/test.js b/assets/eip-5496/test/test.js deleted file mode 100644 index f8d47d5..0000000 --- a/assets/eip-5496/test/test.js +++ /dev/null @@ -1,122 +0,0 @@ -const { assert } = require("chai"); -const { expectRevert } = require('@openzeppelin/test-helpers'); - -const ERC5496Demo = artifacts.require("ERC5496Demo"); - -contract("ERC5496", async accounts => { - const Alice = accounts[0]; - const Bob = accounts[1]; - const Tom = accounts[2]; - let demoContract; - - before(async function() { - const instance = await ERC5496Demo.deployed("ERC5496Demo", "EPD"); - demoContract = instance; - await demoContract.mint(1, Alice); - await demoContract.mint(2, Alice); - await demoContract.mint(3, Alice); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(false); - }) - - it("Should set privilege 0 to Bob", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(1, 0, Bob, BigInt(expires)); - - let user_hasP0 = await demoContract.hasPrivilege(1, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 1 should be Bob" - ); - }); - - it("Privilege should belong to the owner by default", async () => { - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Alice , - "Owner of NFT 1 should be Alice" - ); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Alice); - assert.equal( - user_hasP1, - true, - "Privilege 1 of NFT 1 should be Alice" - ); - }); - - it("The privilege holder is allowed to transfer the privilege to others", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(2, 0, Bob, BigInt(expires)); - let user_hasP0 = await demoContract.hasPrivilege(2, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Bob" - ); - await demoContract.setPrivilege(2, 0, Tom, BigInt(expires + 100), { from: Bob }) - user_hasP0 = await demoContract.hasPrivilege(2, 0, Tom); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Tom" - ); - let privilege_info = await demoContract.getPrivilegeInfo(2, 0); - assert.equal( - privilege_info.expiresAt, - expires, - "Only owner can set the expiresAt" - ) - }); - - it("User is allowed to transfer NFT while privileges on renting", async () => { - await demoContract.transferFrom(Alice, Bob, 1); - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Bob, - "Owner of NFT 1 should be Bob" - ); - let expires = Math.floor(new Date().getTime()/1000) + 1000; - await demoContract.setPrivilege(1, 1, Tom, BigInt(expires), { from: Bob }); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Tom); - assert.equal( - user_hasP1, - true, - "Bob should be allowed to set the unassigned privilege to Tom" - ); - }); - - it("NFT owner may change the privileges total for each tokenId", async () => { - await demoContract.increasePrivileges(false); - let owner_1 = await demoContract.ownerOf(1); - let user_hasP2 = await demoContract.hasPrivilege(1, 2, owner_1); - assert.equal( - user_hasP2, - true, - "privilege 2 available after NFT owner update the privilege total" - ); - }); - - it("NFT owner should not change the privilege if it has been assigned", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(3, 0, Bob, BigInt(expires)); - await expectRevert( - demoContract.setPrivilege(3, 0, Tom, BigInt(expires)), - "ERC721: transfer caller is not owner nor approved", - ); - }); - - it("NFT should support interface IERC5496", async () => { - const interfaceIds = { - IERC165: "0x01ffc9a7", - IERC721: "0x80ac58cd", - IERC5496: "0x076e1bbb", - } - for(let interfaceName in interfaceIds) { - let isSupport = await demoContract.supportsInterface(interfaceIds[interfaceName]); - assert.equal(isSupport, true, "NFT should support interface "+interfaceName); - } - }) -}); diff --git a/assets/eip-5496/test/testCloneable.js b/assets/eip-5496/test/testCloneable.js deleted file mode 100644 index 07e316e..0000000 --- a/assets/eip-5496/test/testCloneable.js +++ /dev/null @@ -1,158 +0,0 @@ -const { assert } = require("chai"); -const { expectRevert } = require('@openzeppelin/test-helpers'); - -const ERC5496Demo = artifacts.require("ERC5496CloneableDemo"); - -contract("ERC5496Cloneable", async accounts => { - const Alice = accounts[0]; - const Bob = accounts[1]; - const Tom = accounts[2]; - let demoContract; - - before(async function() { - const instance = await ERC5496Demo.deployed("ERC5496CDemo", "EPCD"); - demoContract = instance; - await demoContract.mint(1, Alice); - await demoContract.mint(2, Alice); - await demoContract.mint(3, Alice); - await demoContract.mint(4, Alice); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(true); - }) - - it("Should set privilege 0 to Bob", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(1, 0, Bob, BigInt(expires)); - - let user_hasP0 = await demoContract.hasPrivilege(1, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 1 should be Bob" - ); - }); - - it("Privilege should belong to the owner by default", async () => { - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Alice , - "Owner of NFT 1 should be Alice" - ); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Alice); - assert.equal( - user_hasP1, - true, - "Privilege 1 of NFT 1 should be Alice" - ); - }); - - it("The privilege holder is allowed to transfer the privilege to others", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(2, 0, Bob, BigInt(expires)); - let user_hasP0 = await demoContract.hasPrivilege(2, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Bob" - ); - await demoContract.setPrivilege(2, 0, Tom, BigInt(expires + 100), { from: Bob }) - user_hasP0 = await demoContract.hasPrivilege(2, 0, Tom); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Tom" - ); - let privilege_info = await demoContract.getPrivilegeInfo(2, 0); - assert.equal( - privilege_info.expiresAt, - expires, - "Only owner can set the expiresAt" - ) - }); - - it("User is allowed to transfer NFT while privileges on renting", async () => { - await demoContract.transferFrom(Alice, Bob, 1); - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Bob, - "Owner of NFT 1 should be Bob" - ); - let expires = Math.floor(new Date().getTime()/1000) + 1000; - await demoContract.setPrivilege(1, 1, Tom, BigInt(expires), { from: Bob }); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Tom); - assert.equal( - user_hasP1, - true, - "Bob should be allowed to set unassigned privilege to Tom" - ); - }); - - it("NFT owner may change the privileges total for each tokenId", async () => { - let owner_1 = await demoContract.ownerOf(1); - let user_hasP2 = await demoContract.hasPrivilege(1, 2, owner_1); - assert.equal( - user_hasP2, - true, - "privilege 2 available after NFT owner update the privilege total" - ); - }); - - it("NFT owner should not change the privilege if it has been assigned", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(3, 0, Bob, BigInt(expires)); - await expectRevert( - demoContract.setPrivilege(3, 0, Tom, BigInt(expires)), - "ERC721: transfer caller is not owner nor approved", - ); - }); - - it("ERC5496 cloneable", async () => { - let owner_1 = await demoContract.ownerOf(4); - let cloneable_P2 = await demoContract.cloneable(2); - assert.equal( - cloneable_P2, - false, - "privilege 2 should not be cloneable" - ); - let cloneable_P3 = await demoContract.cloneable(3); - assert.equal( - cloneable_P3, - true, - "privilege 3 should be cloneable" - ); - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(4, 3, Bob, BigInt(expires), { from: Alice }); - - await expectRevert( - demoContract.clonePrivilege(4, 2, owner_1, {from: Bob}), - "privilege not cloneable", - ); - await expectRevert( - demoContract.clonePrivilege(4, 3, Tom, { from: Bob }), - "referrer not exists", - ); - await demoContract.clonePrivilege(4, 3, Bob, { from: Tom }); - let user_hasP3 = await demoContract.hasPrivilege(4, 3, Tom); - assert.equal( - user_hasP3, - true, - "privilege 3 available after Bob cloned" - ); - }); - - it("NFT should support interface IERC5496", async () => { - const interfaceIds = { - IERC165: "0x01ffc9a7", - IERC721: "0x80ac58cd", - IERC5496: "0x076e1bbb", - } - for(let interfaceName in interfaceIds) { - let isSupport = await demoContract.supportsInterface(interfaceIds[interfaceName]); - assert.equal(isSupport, true, "NFT should support interface "+interfaceName); - } - }) -}); diff --git a/assets/eip-5501/contracts/ERC5501.sol b/assets/eip-5501/contracts/ERC5501.sol deleted file mode 100644 index 6dd2f65..0000000 --- a/assets/eip-5501/contracts/ERC5501.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5501.sol"; - -/** - * @dev Implementation of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501 is IERC5501, ERC721 { - /** - * @dev Structure to hold user information. - * @notice If isBorrowed is true, UserInfo cannot be modified before it expires. - */ - struct UserInfo { - address user; // Address of user role - uint64 expires; // Unix timestamp, user expires on - bool isBorrowed; // Borrowed flag - } - - // Mapping from token ID to UserInfo - mapping(uint256 => UserInfo) internal _users; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC721(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - require( - _isApprovedOrOwner(msg.sender, tokenId), - "ERC5501: set user caller is not token owner or approved" - ); - require(user != address(0), "ERC5501: set user to zero address"); - - UserInfo storage info = _users[tokenId]; - require( - !info.isBorrowed || info.expires < block.timestamp, - "ERC5501: token is borrowed" - ); - info.user = user; - info.expires = expires; - info.isBorrowed = isBorrowed; - emit UpdateUser(tokenId, user, expires, isBorrowed); - } - - /** - * @dev See {IERC5501-userOf}. - */ - function userOf(uint256 tokenId) - public - view - virtual - override - returns (address) - { - require( - uint256(_users[tokenId].expires) >= block.timestamp, - "ERC5501: user does not exist for this token" - ); - return _users[tokenId].user; - } - - /** - * @dev See {IERC5501-userExpires}. - */ - function userExpires(uint256 tokenId) - public - view - virtual - override - returns (uint64) - { - return _users[tokenId].expires; - } - - /** - * @dev See {IERC5501-isBorrowed}. - */ - function userIsBorrowed(uint256 tokenId) - public - view - virtual - override - returns (bool) - { - return _users[tokenId].isBorrowed; - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev Hook that is called after any token transfer. - * If user is set and token is not borrowed, reset user. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._afterTokenTransfer(from, to, tokenId); - if ( - from != to && - !_users[tokenId].isBorrowed && - _users[tokenId].user != address(0) - ) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0, false); - } - } -} diff --git a/assets/eip-5501/contracts/ERC5501Balance.sol b/assets/eip-5501/contracts/ERC5501Balance.sol deleted file mode 100644 index a967ccb..0000000 --- a/assets/eip-5501/contracts/ERC5501Balance.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5501.sol"; -import "./IERC5501Balance.sol"; - -/** - * @dev Implementation of Balance extension of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Balance is IERC5501Balance, ERC5501 { - // Mapping from address to userOf tokens - mapping(address => uint256[]) internal _userBalances; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC5501(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - flushExpired(user); - super.setUser(tokenId, user, expires, isBorrowed); - _userBalances[user].push(tokenId); - } - - /** - * @dev See {IERC5501-userBalanceOf}. - */ - function userBalanceOf(address user) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Balance: address zero is not a valid owner" - ); - uint256 balance; - uint256[] memory candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].expires >= block.timestamp && - _users[candidates[i]].user == user - ) { - ++balance; - } - } - } - return balance; - } - - /** - * @notice On setUser flush all expired userOf statuses. - * @dev This function may revert out of gas if user borrows too many tokens at once. - * There must be a way to prevent such behaviour (such as flushing by parts only). - * @param user an address to flush - */ - function flushExpired(address user) internal { - uint256[] storage candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].user != user || - _users[candidates[i]].expires < block.timestamp - ) { - candidates[i] = candidates[candidates.length - 1]; - candidates.pop(); - --i; // test moved element - } - } - } - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501Balance).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5501/contracts/ERC5501Combined.sol b/assets/eip-5501/contracts/ERC5501Combined.sol deleted file mode 100644 index 95f8b27..0000000 --- a/assets/eip-5501/contracts/ERC5501Combined.sol +++ /dev/null @@ -1,325 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5501.sol"; -import "./IERC5501Balance.sol"; -import "./IERC5501Enumerable.sol"; -import "./IERC5501Terminable.sol"; - -/** - * @dev Implementation of ERC5501 contract with all extensions https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Combined is - IERC5501, - IERC5501Balance, - IERC5501Terminable, - IERC5501Enumerable, - ERC721 -{ - /** - * @dev Structure to hold user information. - * @notice If isBorrowed is true, UserInfo cannot be modified before it expires. - */ - struct UserInfo { - address user; // Address of user role - uint64 expires; // Unix timestamp, user expires on - bool isBorrowed; // Borrowed flag - } - - /** - * @dev Structure to hold agreements from both parties to terminate a borrow. - * @notice If both parties agree, it is possible to modify UserInfo even before it expires. - * In such case, isBorrowed status is reverted to false. - */ - struct BorrowTerminationInfo { - bool lenderAgreement; - bool borrowerAgreement; - } - - // Mapping from token ID to UserInfo - mapping(uint256 => UserInfo) internal _users; - - // Mapping from address to userOf tokens - mapping(address => uint256[]) internal _userBalances; - - // Mapping from token ID to BorrowTerminationInfo - mapping(uint256 => BorrowTerminationInfo) internal _borrowTerminations; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC721(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - // Balance extension - flushExpired(user); - - require( - _isApprovedOrOwner(msg.sender, tokenId), - "ERC5501: set user caller is not token owner or approved" - ); - require(user != address(0), "ERC5501: set user to zero address"); - - UserInfo storage info = _users[tokenId]; - require( - !info.isBorrowed || info.expires < block.timestamp, - "ERC5501: token is borrowed" - ); - info.user = user; - info.expires = expires; - info.isBorrowed = isBorrowed; - emit UpdateUser(tokenId, user, expires, isBorrowed); - - // Balance extension - _userBalances[user].push(tokenId); - // Terminable extension - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - - /** - * @dev See {IERC5501-userOf}. - */ - function userOf(uint256 tokenId) - public - view - virtual - override - returns (address) - { - require( - uint256(_users[tokenId].expires) >= block.timestamp, - "ERC5501: user does not exist for this token" - ); - return _users[tokenId].user; - } - - /** - * @dev See {IERC5501-userBalanceOf}. - */ - function userBalanceOf(address user) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Balance: address zero is not a valid owner" - ); - uint256 balance; - uint256[] memory candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].expires >= block.timestamp && - _users[candidates[i]].user == user - ) { - ++balance; - } - } - } - return balance; - } - - /** - * @dev See {IERC5501-tokenOfUserByIndex}. - */ - function tokenOfUserByIndex(address user, uint256 index) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Enumerable: address zero is not a valid owner" - ); - uint256[] memory balance = _userBalances[user]; - require( - balance.length > 0 && index < balance.length, - "ERC5501Enumerable: owner index out of bounds" - ); - uint256 counter; - unchecked { - for (uint256 i; i < balance.length; ++i) { - if ( - _users[balance[i]].expires >= block.timestamp && - _users[balance[i]].user == user - ) { - if (counter == index) { - return balance[i]; - } - ++counter; - } - } - } - revert("ERC5501Enumerable: owner index out of bounds"); - } - - /** - * @dev See {IERC5501-userExpires}. - */ - function userExpires(uint256 tokenId) - public - view - virtual - override - returns (uint64) - { - return _users[tokenId].expires; - } - - /** - * @dev See {IERC5501-isBorrowed}. - */ - function userIsBorrowed(uint256 tokenId) - public - view - virtual - override - returns (bool) - { - return _users[tokenId].isBorrowed; - } - - /** - * @dev See {IERC5501Terminable-getBorrowTermination}. - */ - function getBorrowTermination(uint256 tokenId) - public - view - virtual - override - returns (bool, bool) - { - return ( - _borrowTerminations[tokenId].lenderAgreement, - _borrowTerminations[tokenId].borrowerAgreement - ); - } - - /** - * @dev See {IERC5501Terminable-setBorrowTermination}. - */ - function setBorrowTermination(uint256 tokenId) public virtual override { - UserInfo storage userInfo = _users[tokenId]; - require( - userInfo.expires >= block.timestamp && userInfo.isBorrowed, - "ERC5501Terminable: borrow not active" - ); - - BorrowTerminationInfo storage terminationInfo = _borrowTerminations[ - tokenId - ]; - if (ownerOf(tokenId) == msg.sender) { - terminationInfo.lenderAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, true); - } - if (userInfo.user == msg.sender) { - terminationInfo.borrowerAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, false); - } - } - - /** - * @dev See {IERC5501Terminable-terminateBorrow}. - */ - function terminateBorrow(uint256 tokenId) public virtual override { - BorrowTerminationInfo storage info = _borrowTerminations[tokenId]; - require( - info.lenderAgreement && info.borrowerAgreement, - "ERC5501Terminable: not agreed" - ); - _users[tokenId].isBorrowed = false; - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - emit TerminateBorrow( - tokenId, - ownerOf(tokenId), - _users[tokenId].user, - msg.sender - ); - } - - /** - * @notice On setUser flush all expired userOf statuses. - * @dev This function may revert out of gas if user borrows too many tokens at once. - * There must be a way to prevent such behaviour (such as flushing by parts only). - * @param user an address to flush - */ - function flushExpired(address user) internal { - uint256[] storage candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].user != user || - _users[candidates[i]].expires < block.timestamp - ) { - candidates[i] = candidates[candidates.length - 1]; - candidates.pop(); - --i; // test moved element - } - } - } - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501).interfaceId || - interfaceId == type(IERC5501Balance).interfaceId || - interfaceId == type(IERC5501Enumerable).interfaceId || - interfaceId == type(IERC5501Terminable).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev Hook that is called after any token transfer. - * If user is set and token is not borrowed, reset user. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._afterTokenTransfer(from, to, tokenId); - if ( - from != to && - !_users[tokenId].isBorrowed && - _users[tokenId].user != address(0) - ) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0, false); - } else if ( - // Terminable extension - from != to && _users[tokenId].isBorrowed - ) { - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - } -} diff --git a/assets/eip-5501/contracts/ERC5501Enumerable.sol b/assets/eip-5501/contracts/ERC5501Enumerable.sol deleted file mode 100644 index 1068cad..0000000 --- a/assets/eip-5501/contracts/ERC5501Enumerable.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5501Balance.sol"; -import "./IERC5501Enumerable.sol"; - -/** - * @dev Implementation of Enumerable extension of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Enumerable is IERC5501Enumerable, ERC5501Balance { - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC5501Balance(name_, symbol_) - {} - - /** - * @dev See {IERC5501-tokenOfUserByIndex}. - */ - function tokenOfUserByIndex(address user, uint256 index) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Enumerable: address zero is not a valid owner" - ); - uint256[] memory balance = _userBalances[user]; - require( - balance.length > 0 && index < balance.length, - "ERC5501Enumerable: owner index out of bounds" - ); - uint256 counter; - unchecked { - for (uint256 i; i < balance.length; ++i) { - if ( - _users[balance[i]].expires >= block.timestamp && - _users[balance[i]].user == user - ) { - if (counter == index) { - return balance[i]; - } - ++counter; - } - } - } - revert("ERC5501Enumerable: owner index out of bounds"); - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501Enumerable).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5501/contracts/ERC5501Terminable.sol b/assets/eip-5501/contracts/ERC5501Terminable.sol deleted file mode 100644 index 938ef78..0000000 --- a/assets/eip-5501/contracts/ERC5501Terminable.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5501.sol"; -import "./IERC5501Terminable.sol"; - -/** - * @dev Implementation of Terminable extension of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Terminable is IERC5501Terminable, ERC5501 { - /** - * @dev Structure to hold agreements from both parties to terminate a borrow. - * @notice If both parties agree, it is possible to modify UserInfo even before it expires. - * In such case, isBorrowed status is reverted to false. - */ - struct BorrowTerminationInfo { - bool lenderAgreement; - bool borrowerAgreement; - } - - // Mapping from token ID to BorrowTerminationInfo - mapping(uint256 => BorrowTerminationInfo) internal _borrowTerminations; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC5501(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - super.setUser(tokenId, user, expires, isBorrowed); - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - - /** - * @dev See {IERC5501Terminable-setBorrowTermination}. - */ - function setBorrowTermination(uint256 tokenId) public virtual override { - UserInfo storage userInfo = _users[tokenId]; - require( - userInfo.expires >= block.timestamp && userInfo.isBorrowed, - "ERC5501Terminable: borrow not active" - ); - - BorrowTerminationInfo storage terminationInfo = _borrowTerminations[ - tokenId - ]; - if (ownerOf(tokenId) == msg.sender) { - terminationInfo.lenderAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, true); - } - if (userInfo.user == msg.sender) { - terminationInfo.borrowerAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, false); - } - } - - /** - * @dev See {IERC5501Terminable-getBorrowTermination}. - */ - function getBorrowTermination(uint256 tokenId) - public - view - virtual - override - returns (bool, bool) - { - return ( - _borrowTerminations[tokenId].lenderAgreement, - _borrowTerminations[tokenId].borrowerAgreement - ); - } - - /** - * @dev See {IERC5501Terminable-terminateBorrow}. - */ - function terminateBorrow(uint256 tokenId) public virtual override { - BorrowTerminationInfo storage info = _borrowTerminations[tokenId]; - require( - info.lenderAgreement && info.borrowerAgreement, - "ERC5501Terminable: not agreed" - ); - _users[tokenId].isBorrowed = false; - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - emit TerminateBorrow( - tokenId, - ownerOf(tokenId), - _users[tokenId].user, - msg.sender - ); - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501Terminable).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev Hook that is called after any token transfer. - * If user is set and token is borrowed, reset termination agreements. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._afterTokenTransfer(from, to, tokenId); - if (from != to && _users[tokenId].isBorrowed) { - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - } -} diff --git a/assets/eip-5501/contracts/IERC5501.sol b/assets/eip-5501/contracts/IERC5501.sol deleted file mode 100644 index 37626bd..0000000 --- a/assets/eip-5501/contracts/IERC5501.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501: Rental & Delegation NFT - EIP-721 Extension - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * @notice the EIP-165 identifier for this interface is 0xf808ec37. - */ -interface IERC5501 /* is IERC721 */ { - /** - * @dev Emitted when the user of an NFT is modified. - */ - event UpdateUser(uint256 indexed _tokenId, address indexed _user, uint64 _expires, bool _isBorrowed); - - /** - * @notice Set the user info of an NFT. - * @dev User address cannot be zero address. - * Only approved operator or NFT owner can set the user. - * If NFT is borrowed, the user info cannot be changed until user status expires. - * @param _tokenId uint256 ID of the token to set user info for - * @param _user address of the new user - * @param _expires Unix timestamp when user info expires - * @param _isBorrowed flag whether or not the NFT is borrowed - */ - function setUser(uint256 _tokenId, address _user, uint64 _expires, bool _isBorrowed) external; - - /** - * @notice Get the user address of an NFT. - * @dev Reverts if user is not set. - * @param _tokenId uint256 ID of the token to get the user address for - * @return address user address for this NFT - */ - function userOf(uint256 _tokenId) external view returns (address); - - /** - * @notice Get the user expires of an NFT. - * @param _tokenId uint256 ID of the token to get the user expires for - * @return uint64 user expires for this NFT - */ - function userExpires(uint256 _tokenId) external view returns (uint64); - - /** - * @notice Get the user isBorrowed of an NFT. - * @param _tokenId uint256 ID of the token to get the user isBorrowed for - * @return bool user isBorrowed for this NFT - */ - function userIsBorrowed(uint256 _tokenId) external view returns (bool); -} diff --git a/assets/eip-5501/contracts/IERC5501Balance.sol b/assets/eip-5501/contracts/IERC5501Balance.sol deleted file mode 100644 index fa32ec8..0000000 --- a/assets/eip-5501/contracts/IERC5501Balance.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501Balance - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * Extension for ERC5501 which adds userBalanceOf to query how many tokens address is userOf. - * @notice the EIP-165 identifier for this interface is 0x0cb22289. - */ -interface IERC5501Balance /* is IERC5501 */{ - /** - * @notice Count of all NFTs assigned to a user. - * @dev Reverts if user is zero address. - * @param _user an address for which to query the balance - * @return uint256 the number of NFTs the user has - */ - function userBalanceOf(address _user) external view returns (uint256); -} diff --git a/assets/eip-5501/contracts/IERC5501Enumerable.sol b/assets/eip-5501/contracts/IERC5501Enumerable.sol deleted file mode 100644 index 8780007..0000000 --- a/assets/eip-5501/contracts/IERC5501Enumerable.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501Enumerable - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * This extension for ERC5501 adds the option to iterate over user tokens. - * @notice the EIP-165 identifier for this interface is 0x1d350ef8. - */ -interface IERC5501Enumerable /* is IERC5501Balance, IERC5501 */ { - /** - * @notice Enumerate NFTs assigned to a user. - * @dev Reverts if user is zero address or _index >= userBalanceOf(_owner). - * @param _user an address to iterate over its tokens - * @return uint256 the token ID for given index assigned to _user - */ - function tokenOfUserByIndex(address _user, uint256 _index) external view returns (uint256); -} diff --git a/assets/eip-5501/contracts/IERC5501Terminable.sol b/assets/eip-5501/contracts/IERC5501Terminable.sol deleted file mode 100644 index 6f74bb3..0000000 --- a/assets/eip-5501/contracts/IERC5501Terminable.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501Terminable - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * This extension for ERC5501 adds the option to terminate borrowing if both parties agree. - * @notice the EIP-165 identifier for this interface is 0x6a26417e. - */ -interface IERC5501Terminable /* is IERC5501 */ { - /** - * @dev Emitted when one party from borrowing contract approves termination of agreement. - * @param _isLender true for lender, false for borrower - */ - event AgreeToTerminateBorrow(uint256 indexed _tokenId, address indexed _party, bool _isLender); - - /** - * @dev Emitted when agreements to terminate borrow are reset. - */ - event ResetTerminationAgreements(uint256 indexed _tokenId); - - /** - * @dev Emitted when borrow of token ID is terminated. - */ - event TerminateBorrow(uint256 indexed _tokenId, address indexed _lender, address indexed _borrower, address _caller); - - /** - * @notice Agree to terminate a borrowing. - * @dev Lender must be ownerOf token ID. Borrower must be userOf token ID. - * If lender and borrower are the same, set termination agreement for both at once. - * @param _tokenId uint256 ID of the token to set termination info for - */ - function setBorrowTermination(uint256 _tokenId) external; - - /** - * @notice Get if it is possible to terminate a borrow agreement. - * @param _tokenId uint256 ID of the token to get termination info for - * @return bool, bool first indicates lender agrees, second indicates borrower agrees - */ - function getBorrowTermination(uint256 _tokenId) external view returns (bool, bool); - - /** - * @notice Terminate a borrow if both parties agreed. - * @dev Both parties must have agreed, otherwise revert. - * @param _tokenId uint256 ID of the token to terminate borrow of - */ - function terminateBorrow(uint256 _tokenId) external; -} diff --git a/assets/eip-5501/contracts/test/ERC5501BalanceTestCollection.sol b/assets/eip-5501/contracts/test/ERC5501BalanceTestCollection.sol deleted file mode 100644 index 38057df..0000000 --- a/assets/eip-5501/contracts/test/ERC5501BalanceTestCollection.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Balance.sol"; - -contract ERC5501BalanceTestCollection is ERC5501Balance { - - constructor(string memory name_, string memory symbol_) ERC5501Balance(name_,symbol_) {} - - function getUserBalances(address user) external view returns (uint256[] memory) { - return _userBalances[user]; - } - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501CombinedCollection.sol b/assets/eip-5501/contracts/test/ERC5501CombinedCollection.sol deleted file mode 100644 index 2a2c346..0000000 --- a/assets/eip-5501/contracts/test/ERC5501CombinedCollection.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Combined.sol"; - -contract ERC5501CombinedTestCollection is ERC5501Combined { - - constructor(string memory name_, string memory symbol_) ERC5501Combined(name_,symbol_) {} - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501EnumerableTestCollection.sol b/assets/eip-5501/contracts/test/ERC5501EnumerableTestCollection.sol deleted file mode 100644 index eed0b4b..0000000 --- a/assets/eip-5501/contracts/test/ERC5501EnumerableTestCollection.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Enumerable.sol"; - -contract ERC5501EnumerableTestCollection is ERC5501Enumerable { - - constructor(string memory name_, string memory symbol_) ERC5501Enumerable(name_,symbol_) {} - - function getUserBalances(address user) external view returns (uint256[] memory) { - return _userBalances[user]; - } - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501TerminableTestCollection.sol b/assets/eip-5501/contracts/test/ERC5501TerminableTestCollection.sol deleted file mode 100644 index 1ba6f8e..0000000 --- a/assets/eip-5501/contracts/test/ERC5501TerminableTestCollection.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Terminable.sol"; - -contract ERC5501TerminableTestCollection is ERC5501Terminable { - - constructor(string memory name_, string memory symbol_) ERC5501Terminable(name_,symbol_) {} - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501TestCollection.sol b/assets/eip-5501/contracts/test/ERC5501TestCollection.sol deleted file mode 100644 index 67429ad..0000000 --- a/assets/eip-5501/contracts/test/ERC5501TestCollection.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501.sol"; - -contract ERC5501TestCollection is ERC5501 { - - constructor(string memory name_, string memory symbol_) ERC5501(name_,symbol_) {} - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/test/ERC5501BalanceTest.ts b/assets/eip-5501/test/ERC5501BalanceTest.ts deleted file mode 100644 index a936ca0..0000000 --- a/assets/eip-5501/test/ERC5501BalanceTest.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; - -describe("ERC5501BalanceTest", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - // Fri Jan 01 2021 00:00:00 GMT+0000 - const expired = 1609459200; - - const expires = (await time.latest()) + fastForwardYear - 1; - - const [owner, delegatee] = await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501BalanceTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - await contract.mint(owner.address, 2); - await contract.mint(owner.address, 3); - await contract.mint(owner.address, 4); - await contract.mint(owner.address, 5); - await contract.mint(owner.address, 6); - await contract.mint(owner.address, 7); - - return { contract, owner, delegatee, expires, expired, fastForwardYear }; - } - - it("Returns correct balance of user", async function () { - const { contract, owner, delegatee, expires, expired, fastForwardYear } = - await loadFixture(initialize); - - await contract.setUser(1, delegatee.address, expires, false); - await contract.setUser(2, delegatee.address, expires, false); - await contract.setUser(3, delegatee.address, expires, false); - await contract.setUser(4, delegatee.address, expired, false); - await contract.setUser(5, delegatee.address, expired, false); - await contract.setUser(6, delegatee.address, expired, false); - await contract.setUser(7, delegatee.address, expired, false); - - // flush function is called for user parameter - meaning flush does not happen for delegatee if user parameter is different address - await contract.setUser(2, owner.address, expires, false); - // delegatee is user of 1, 3 - // delegatee balances array is 1, 2, 3, 7 - - expect(await contract.userBalanceOf(delegatee.address)).to.equal(2); - expect(await contract.getUserBalances(delegatee.address)).to.deep.equal([ - BigNumber.from("1"), - BigNumber.from("2"), - BigNumber.from("3"), - BigNumber.from("7"), - ]); - - await time.increaseTo((await time.latest()) + fastForwardYear); - await contract.setUser( - 1, - delegatee.address, - expires + fastForwardYear, - false - ); - - expect(await contract.userBalanceOf(delegatee.address)).to.equal(1); - expect(await contract.getUserBalances(delegatee.address)).to.deep.equal([ - BigNumber.from("1"), - ]); - - await time.increaseTo((await time.latest()) + fastForwardYear); - expect(await contract.userBalanceOf(delegatee.address)).to.equal(0); - }); - - it("Revert user balance query for zero address", async function () { - const { contract } = await loadFixture(initialize); - - await expect( - contract.userBalanceOf(ethers.constants.AddressZero) - ).to.be.revertedWith("ERC5501Balance: address zero is not a valid owner"); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0x0cb22289")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501CombinedTest.ts b/assets/eip-5501/test/ERC5501CombinedTest.ts deleted file mode 100644 index da8bb8d..0000000 --- a/assets/eip-5501/test/ERC5501CombinedTest.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; - -describe("ERC5501CombinedTest", function () { - async function initialize() { - // 7 * 24 * 60 * 60 - const week = 604800; - - const uint64MaxValue = BigNumber.from("18446744073709551615"); - - const [owner, delegatee, borrower, rentalContractMock] = - await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501CombinedTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - - return { - contract, - owner, - delegatee, - borrower, - rentalContractMock, - week, - uint64MaxValue, - }; - } - - it("Scenario", async function () { - const { - contract, - owner, - delegatee, - borrower, - rentalContractMock, - week, - uint64MaxValue, - } = await loadFixture(initialize); - - // owner delegates NFT to hot wallet for security - await expect(contract.setUser(1, delegatee.address, uint64MaxValue, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, uint64MaxValue, false); - expect(await contract.userBalanceOf(delegatee.address)).to.equal(1); - expect(await contract.userOf(1)).to.equal(delegatee.address); - expect(await contract.tokenOfUserByIndex(delegatee.address, 0)).to.equal(1); - expect(await contract.userExpires(1)).to.equal(uint64MaxValue); - expect(await contract.userIsBorrowed(1)).to.equal(false); - - // owner then decides to lend the NFT for one week - await contract.setApprovalForAll(rentalContractMock.address, true); - const oneWeekLater = (await time.latest()) + week; - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, oneWeekLater, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, oneWeekLater, true); - expect(await contract.userBalanceOf(delegatee.address)).to.equal(0); - expect(await contract.userBalanceOf(borrower.address)).to.equal(1); - expect(await contract.tokenOfUserByIndex(borrower.address, 0)).to.equal(1); - expect(await contract.userOf(1)).to.equal(borrower.address); - expect(await contract.userExpires(1)).to.equal(oneWeekLater); - expect(await contract.userIsBorrowed(1)).to.equal(true); - - // borrow expires - await time.increaseTo((await time.latest()) + oneWeekLater + 1); - - // owner decides to lend the NFT again - // this time, they accidentally set wrong time - // the owner and borrower agree to terminate the loan under certain conditions - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, uint64MaxValue, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, uint64MaxValue, true); - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - await expect(contract.terminateBorrow(1)) - .to.emit(contract, "TerminateBorrow") - .withArgs(1, owner.address, borrower.address, owner.address) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0xf808ec37")).to.equal(true); - expect(await contract.supportsInterface("0x0cb22289")).to.equal(true); - expect(await contract.supportsInterface("0x1d350ef8")).to.equal(true); - expect(await contract.supportsInterface("0x6a26417e")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501EnumerableTest.ts b/assets/eip-5501/test/ERC5501EnumerableTest.ts deleted file mode 100644 index 9717910..0000000 --- a/assets/eip-5501/test/ERC5501EnumerableTest.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("ERC5501EnumerableTest", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - // allows to set multiple tokens which will expire after fastForwardYear - const expired = (await time.latest()) + fastForwardYear - 1; - - const expires = (await time.latest()) + fastForwardYear + fastForwardYear; - - const [owner, delegatee] = await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501EnumerableTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - await contract.mint(owner.address, 2); - await contract.mint(owner.address, 3); - await contract.mint(owner.address, 4); - await contract.mint(owner.address, 5); - await contract.mint(owner.address, 6); - await contract.mint(owner.address, 7); - - return { contract, owner, delegatee, expires, expired, fastForwardYear }; - } - - it("Return correct user tokens by index", async function () { - const { contract, owner, delegatee, expires, expired, fastForwardYear } = - await loadFixture(initialize); - - await contract.setUser(1, delegatee.address, expires, false); - await contract.setUser(2, delegatee.address, expired, false); - await contract.setUser(3, delegatee.address, expires, false); - await contract.setUser(4, delegatee.address, expired, false); - await contract.setUser(5, delegatee.address, expires, false); - await contract.setUser(6, delegatee.address, expired, false); - await contract.setUser(7, delegatee.address, expires, false); - - expect(await contract.tokenOfUserByIndex(delegatee.address, 0)).to.equal(1); - expect(await contract.tokenOfUserByIndex(delegatee.address, 1)).to.equal(2); - expect(await contract.tokenOfUserByIndex(delegatee.address, 2)).to.equal(3); - expect(await contract.tokenOfUserByIndex(delegatee.address, 3)).to.equal(4); - expect(await contract.tokenOfUserByIndex(delegatee.address, 4)).to.equal(5); - expect(await contract.tokenOfUserByIndex(delegatee.address, 5)).to.equal(6); - expect(await contract.tokenOfUserByIndex(delegatee.address, 6)).to.equal(7); - - // fast forward one year, token 2, 4, 6 expired for user - // current balance: 1, 3, 5, 7 - await time.increaseTo((await time.latest()) + fastForwardYear); - - expect(await contract.tokenOfUserByIndex(delegatee.address, 0)).to.equal(1); - expect(await contract.tokenOfUserByIndex(delegatee.address, 1)).to.equal(3); - expect(await contract.tokenOfUserByIndex(delegatee.address, 2)).to.equal(5); - expect(await contract.tokenOfUserByIndex(delegatee.address, 3)).to.equal(7); - await expect( - contract.tokenOfUserByIndex(delegatee.address, 4) - ).to.be.revertedWith("ERC5501Enumerable: owner index out of bounds"); - }); - - it("Revert user token id by index query for zero address", async function () { - const { contract } = await loadFixture(initialize); - - await expect( - contract.tokenOfUserByIndex(ethers.constants.AddressZero, 0) - ).to.be.revertedWith("ERC5501Enumerable: address zero is not a valid owner"); - }); - - it("Revert user token id by index query for out of bounds index", async function () { - const { contract, delegatee } = await loadFixture(initialize); - - await expect( - contract.tokenOfUserByIndex(delegatee.address, 0) - ).to.be.revertedWith("ERC5501Enumerable: owner index out of bounds"); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0x1d350ef8")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501TerminableTest.ts b/assets/eip-5501/test/ERC5501TerminableTest.ts deleted file mode 100644 index c3da145..0000000 --- a/assets/eip-5501/test/ERC5501TerminableTest.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; - -describe("ERC5501TerminableTest", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - - const expires = (await time.latest()) + fastForwardYear - 1; - - const uint64MaxValue = BigNumber.from("18446744073709551615"); - - const [owner, delegatee, borrower] = await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501TerminableTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - - return { - contract, - owner, - delegatee, - borrower, - uint64MaxValue, - expires, - fastForwardYear, - }; - } - - it("Cannot terminate borrow without approval of both parties", async function () { - const { contract, borrower, uint64MaxValue } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, borrower.address, uint64MaxValue, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, uint64MaxValue, true); - await expect(contract.terminateBorrow(1)).to.be.revertedWith( - "ERC5501Terminable: not agreed" - ); - }); - - it("Cannot set borrow termination if borrow is not active", async function () { - const { contract, delegatee, uint64MaxValue } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, delegatee.address, uint64MaxValue, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, uint64MaxValue, false); - await expect(contract.setBorrowTermination(1)).to.be.revertedWith( - "ERC5501Terminable: borrow not active" - ); - }); - - it("Can reset borrow if owner mistakenly borrowed token to own wallet and set a long duration", async function () { - const { contract, owner, uint64MaxValue } = await loadFixture(initialize); - - await expect(contract.setUser(1, owner.address, uint64MaxValue, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, owner.address, uint64MaxValue, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await expect(contract.terminateBorrow(1)) - .to.emit(contract, "TerminateBorrow") - .withArgs(1, owner.address, owner.address, owner.address) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - expect(await contract.userIsBorrowed(1)).to.equal(false); - }); - - it("Can reset borrow and set a new user if both parties agree", async function () { - const { contract, owner, delegatee, borrower, uint64MaxValue } = - await loadFixture(initialize); - - await expect(contract.setUser(1, borrower.address, uint64MaxValue, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, uint64MaxValue, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - false, - ]); - - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await expect(contract.terminateBorrow(1)) - .to.emit(contract, "TerminateBorrow") - .withArgs(1, owner.address, borrower.address, owner.address) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - expect(await contract.userIsBorrowed(1)).to.equal(false); - - await expect(contract.setUser(1, delegatee.address, 0, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, 0, false); - }); - - it("Agreed borrow terminations must be reset if userOf is changed", async function () { - const { contract, owner, delegatee, borrower, expires, fastForwardYear } = - await loadFixture(initialize); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await time.increaseTo((await time.latest()) + fastForwardYear); - - await expect(contract.setUser(1, delegatee.address, 0, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, 0, false) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - }); - - it("Reset termination agreements if token is transferred", async function () { - const { contract, owner, delegatee, borrower, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await expect( - contract["safeTransferFrom(address,address,uint256)"]( - owner.address, - delegatee.address, - 1 - ) - ) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0x6a26417e")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501Test.ts b/assets/eip-5501/test/ERC5501Test.ts deleted file mode 100644 index 0be415a..0000000 --- a/assets/eip-5501/test/ERC5501Test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("ERC5501Test", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - - const expires = (await time.latest()) + fastForwardYear - 1; - - const [owner, delegatee, borrower, rentalContractMock] = - await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501TestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - - return { - contract, - owner, - delegatee, - borrower, - rentalContractMock, - expires, - fastForwardYear, - }; - } - - it("Operator is not owner or approved", async function () { - const { contract, borrower } = await loadFixture(initialize); - - await expect( - contract.connect(borrower).setUser(1, borrower.address, 0, false) - ).to.be.revertedWith( - "ERC5501: set user caller is not token owner or approved" - ); - }); - - it("User cannot be zero address", async function () { - const { contract } = await loadFixture(initialize); - - await expect( - contract.setUser(1, ethers.constants.AddressZero, 0, false) - ).to.be.revertedWith("ERC5501: set user to zero address"); - }); - - it("Revert userOf if not set or expired", async function () { - const { contract } = await loadFixture(initialize); - - await expect(contract.userOf(1)).to.be.revertedWith( - "ERC5501: user does not exist for this token" - ); - }); - - it("Cannot set user if NFT is borrowed", async function () { - const { contract, delegatee, borrower, rentalContractMock, expires } = - await loadFixture(initialize); - - await contract.setApprovalForAll(rentalContractMock.address, true); - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, expires, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - await expect( - contract.setUser(1, delegatee.address, 0, false) - ).to.be.revertedWith("ERC5501: token is borrowed"); - }); - - it("Can delegate and redelegate user", async function () { - const { contract, owner, delegatee, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, owner.address, expires, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, owner.address, expires, false); - await expect(contract.setUser(1, delegatee.address, expires, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, expires, false); - }); - - it("Can set user after borrow expires", async function () { - const { contract, delegatee, borrower, expires, fastForwardYear } = - await loadFixture(initialize); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - await time.increaseTo((await time.latest()) + fastForwardYear); - await expect(contract.setUser(1, delegatee.address, 0, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, 0, false); - }); - - it("User is reset if NFT is not borrowed and transferred", async function () { - const { contract, owner, delegatee, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, delegatee.address, expires, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, expires, false); - await expect( - contract["safeTransferFrom(address,address,uint256)"]( - owner.address, - delegatee.address, - 1 - ) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, ethers.constants.AddressZero, 0, false); - - await expect(contract.userOf(1)).to.be.revertedWith( - "ERC5501: user does not exist for this token" - ); - expect(await contract.userExpires(1)).to.equal(0); - expect(await contract.userIsBorrowed(1)).to.equal(false); - }); - - it("User is not reset if NFT is borrowed and transferred", async function () { - const { contract, owner, delegatee, borrower, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - await contract["safeTransferFrom(address,address,uint256)"]( - owner.address, - delegatee.address, - 1 - ); - - expect(await contract.userOf(1)).to.equal(borrower.address); - expect(await contract.userExpires(1)).to.equal(expires); - expect(await contract.userIsBorrowed(1)).to.equal(true); - }); - - it("Rental contract can set user", async function () { - const { contract, borrower, rentalContractMock, expires } = - await loadFixture(initialize); - - await contract.setApprovalForAll(rentalContractMock.address, true); - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, expires, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - - expect(await contract.userOf(1)).to.equal(borrower.address); - expect(await contract.userExpires(1)).to.equal(expires); - expect(await contract.userIsBorrowed(1)).to.equal(true); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0xf808ec37")).to.equal(true); - }); -}); diff --git a/assets/eip-5516/ERC5516.sol b/assets/eip-5516/ERC5516.sol deleted file mode 100644 index 8e4a6e4..0000000 --- a/assets/eip-5516/ERC5516.sol +++ /dev/null @@ -1,819 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 - -/** - * @notice Reference implementation of the eip-5516 interface. - * Note: this implementation only allows for each user to own only 1 token type for each `id`. - * @author Matias Arazi , Lucas Martín Grasso Ramos - * See https://github.com/ethereum/EIPs/pull/5516 - * - */ - -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "./IERC5516.sol"; - -contract ERC5516 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC5516 { - using Address for address; - - // Used for making each token unique, Maintains ID registry and quantity of tokens minted. - uint256 private nonce; - - // Used as the URI for all token types by relying on ID substitution, e.g. https://ipfs.io/ipfs/token.data - string private _uri; - - // Mapping from token ID to account balances - mapping(address => mapping(uint256 => bool)) private _balances; - - // Mapping from address to mapping id bool that states if address has tokens(under id) awaiting to be claimed - mapping(address => mapping(uint256 => bool)) private _pendings; - - // Mapping from account to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // Mapping from ID to minter address. - mapping(uint256 => address) private _tokenMinters; - - // Mapping from ID to URI. - mapping(uint256 => string) private _tokenURIs; - - /** - * @dev Sets base uri for tokens. Preferably "https://ipfs.io/ipfs/" - */ - constructor(string memory uri_) { - _uri = uri_; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC165, IERC165) - returns (bool) - { - return - interfaceId == type(IERC1155).interfaceId || - interfaceId == type(IERC1155MetadataURI).interfaceId || - interfaceId == type(IERC5516).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev See {IERC1155MetadataURI-uri}. - */ - function uri(uint256 _id) - external - view - virtual - override - returns (string memory) - { - return string(abi.encodePacked(_uri, _tokenURIs[_id])); - } - - /** - * @dev See {IERC1155-balanceOf}. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - */ - function balanceOf(address account, uint256 id) - public - view - virtual - override - returns (uint256) - { - require(account != address(0), "EIP5516: Address zero error"); - if (_balances[account][id]) { - return 1; - } else { - return 0; - } - } - - /** - * @dev See {IERC1155-balanceOfBatch}. - * - * Requirements: - * - * - `accounts` and `ids` must have the same length. - * - */ - function balanceOfBatch(address[] memory accounts, uint256[] memory ids) - public - view - virtual - override - returns (uint256[] memory) - { - require( - accounts.length == ids.length, - "EIP5516: Array lengths mismatch" - ); - - uint256[] memory batchBalances = new uint256[](accounts.length); - - for (uint256 i = 0; i < accounts.length; ++i) { - batchBalances[i] = balanceOf(accounts[i], ids[i]); - } - - return batchBalances; - } - - /** - * @dev Get tokens owned by a given address - * - * Requirements: - * - * - `account` cannot be the zero address. - * - */ - function tokensFrom(address account) - public - view - virtual - override - returns (uint256[] memory) - { - require(account != address(0), "EIP5516: Address zero error"); - - uint256 _tokenCount = 0; - for (uint256 i = 1; i <= nonce; ) { - if (_balances[account][i]) { - unchecked { - ++_tokenCount; - } - } - unchecked { - ++i; - } - } - - uint256[] memory _ownedTokens = new uint256[](_tokenCount); - - for (uint256 i = 1; i <= nonce; ) { - if (_balances[account][i]) { - _ownedTokens[--_tokenCount] = i; - } - unchecked { - ++i; - } - } - - return _ownedTokens; - } - - /** - * @dev Get tokens marked as _pendings of a given address - * - * Requirements: - * - * - `account` cannot be the zero address. - * - */ - function pendingFrom(address account) - public - view - virtual - override - returns (uint256[] memory) - { - require(account != address(0), "EIP5516: Address zero error"); - - uint256 _tokenCount = 0; - - for (uint256 i = 1; i <= nonce; ) { - if (_pendings[account][i]) { - ++_tokenCount; - } - unchecked { - ++i; - } - } - - uint256[] memory _pendingTokens = new uint256[](_tokenCount); - - for (uint256 i = 1; i <= nonce; ) { - if (_pendings[account][i]) { - _pendingTokens[--_tokenCount] = i; - } - unchecked { - ++i; - } - } - - return _pendingTokens; - } - - /** - * @dev See {IERC1155-setApprovalForAll}. - */ - function setApprovalForAll(address operator, bool approved) - public - virtual - override - { - _setApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @dev See {IERC1155-isApprovedForAll}. - */ - function isApprovedForAll(address account, address operator) - public - view - virtual - override - returns (bool) - { - return _operatorApprovals[account][operator]; - } - - /** - * @dev mints(creates) a token - */ - function _mint(address account, string memory data) internal virtual { - unchecked { - ++nonce; - } - - address operator = _msgSender(); - uint256[] memory ids = _asSingletonArray(nonce); - uint256[] memory amounts = _asSingletonArray(1); - bytes memory _bData = bytes(data); - - _beforeTokenTransfer( - operator, - address(0), - operator, - ids, - amounts, - _bData - ); - _tokenURIs[nonce] = data; - _tokenMinters[nonce] = account; - emit TransferSingle(operator, address(0), operator, nonce, 1); - _afterTokenTransfer( - operator, - address(0), - operator, - ids, - amounts, - _bData - ); - } - - /** - * @dev See {IERC1155-safeTransferFrom}. - * - * Requirements: - * - * - `from` must be the creator(minter) of `id` or must have allowed _msgSender() as an operator. - * - */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public virtual override { - require(amount == 1, "EIP5516: Can only transfer one token"); - require( - _msgSender() == _tokenMinters[id] || - isApprovedForAll(_tokenMinters[id], _msgSender()), - "EIP5516: Unauthorized" - ); - - _safeTransferFrom(from, to, id, amount, data); - } - - /** - * @dev See {eip-5516-batchTransfer} - * - * Requirements: - * - * - 'from' must be the creator(minter) of `id` or must have allowed _msgSender() as an operator. - * - */ - function batchTransfer( - address from, - address[] memory to, - uint256 id, - uint256 amount, - bytes memory data - ) external virtual override { - require(amount == 1, "EIP5516: Can only transfer one token"); - require( - _msgSender() == _tokenMinters[id] || - isApprovedForAll(_tokenMinters[id], _msgSender()), - "EIP5516: Unauthorized" - ); - - _batchTransfer(from, to, id, amount, data); - } - - /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. - * - * Emits a {TransferSingle} event. - * - * Requirements: - * - * - `from` must be the creator(minter) of the token under `id`. - * - `to` must be non-zero. - * - `to` must have the token `id` marked as _pendings. - * - `to` must not own a token type under `id`. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the - * acceptance magic value. - * - */ - function _safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) internal virtual { - require(from != address(0), "EIP5516: Address zero error"); - require( - _pendings[to][id] == false && _balances[to][id] == false, - "EIP5516: Already Assignee" - ); - - address operator = _msgSender(); - - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(amount); - - _beforeTokenTransfer(operator, from, to, ids, amounts, data); - - _pendings[to][id] = true; - - emit TransferSingle(operator, from, to, id, amount); - _afterTokenTransfer(operator, from, to, ids, amounts, data); - - _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); - } - - /** - * Transfers `id` token from `from` to every address at `to[]`. - * - * Requirements: - * - See {eip-5516-safeMultiTransfer}. - * - */ - function _batchTransfer( - address from, - address[] memory to, - uint256 id, - uint256 amount, - bytes memory data - ) internal virtual { - address operator = _msgSender(); - - _beforeBatchedTokenTransfer(operator, from, to, id, data); - - for (uint256 i = 0; i < to.length; ) { - address _to = to[i]; - - require(_to != address(0), "EIP5516: Address zero error"); - require( - _pendings[_to][id] == false && _balances[_to][id] == false, - "EIP5516: Already Assignee" - ); - - _pendings[_to][id] = true; - - unchecked { - ++i; - } - } - - emit TransferMulti(operator, from, to, amount, id); - - _beforeBatchedTokenTransfer(operator, from, to, id, data); - } - - /** - * @dev See {eip-5516-claimOrReject} - * - * If action == true: Claims pending token under `id`. - * Else, rejects pending token under `id`. - * - */ - function claimOrReject( - address account, - uint256 id, - bool action - ) external virtual override { - require(_msgSender() == account, "EIP5516: Unauthorized"); - - _claimOrReject(account, id, action); - } - - /** - * @dev See {eip-5516-claimOrReject} - * - * For each `id` - `action` pair: - * - * If action == true: Claims pending token under `id`. - * Else, rejects pending token under `id`. - * - */ - function claimOrRejectBatch( - address account, - uint256[] memory ids, - bool[] memory actions - ) external virtual override { - require( - ids.length == actions.length, - "EIP5516: Array lengths mismatch" - ); - - require(_msgSender() == account, "EIP5516: Unauthorized"); - - _claimOrRejectBatch(account, ids, actions); - } - - /** - * @dev Claims or Reject pending token under `_id` from address `_account`. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have a _pendings token under `id` at the moment of call. - * - `account` mUST not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function _claimOrReject( - address account, - uint256 id, - bool action - ) internal virtual { - require( - _pendings[account][id] == true && _balances[account][id] == false, - "EIP5516: Not claimable" - ); - - address operator = _msgSender(); - - bool[] memory actions = new bool[](1); - actions[0] = action; - uint256[] memory ids = _asSingletonArray(id); - - _beforeTokenClaim(operator, account, actions, ids); - - _balances[account][id] = action; - _pendings[account][id] = false; - - delete _pendings[account][id]; - - emit TokenClaimed(operator, account, actions, ids); - - _afterTokenClaim(operator, account, actions, ids); - } - - /** - * @dev Claims or Reject _pendings `_id` from address `_account`. - * - * For each `id`-`action` pair: - * - * Requirements: - * - `account` cannot be the zero address. - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function _claimOrRejectBatch( - address account, - uint256[] memory ids, - bool[] memory actions - ) internal virtual { - uint256 totalIds = ids.length; - address operator = _msgSender(); - - _beforeTokenClaim(operator, account, actions, ids); - - for (uint256 i = 0; i < totalIds; ) { - uint256 id = ids[i]; - - require( - _pendings[account][id] == true && - _balances[account][id] == false, - "EIP5516: Not claimable" - ); - - _balances[account][id] = actions[i]; - _pendings[account][id] = false; - - delete _pendings[account][id]; - - unchecked { - ++i; - } - } - - emit TokenClaimed(operator, account, actions, ids); - - _afterTokenClaim(operator, account, actions, ids); - } - - /** - * @dev Destroys `id` token from `account` - * - * Emits a {TransferSingle} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` must own a token under `id`. - * - */ - function _burn(address account, uint256 id) internal virtual { - require(_balances[account][id] == true, "EIP5516: Unauthorized"); - - address operator = _msgSender(); - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(1); - - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - - delete _balances[account][id]; - - emit TransferSingle(operator, account, address(0), id, 1); - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - } - - /** - * @dev Destroys all tokens under `ids` from `account` - * - * Emits a {TransferBatch} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` must own all tokens under `ids`. - * - */ - function _burnBatch(address account, uint256[] memory ids) - internal - virtual - { - uint256 totalIds = ids.length; - address operator = _msgSender(); - uint256[] memory amounts = _asSingletonArray(totalIds); - uint256[] memory values = _asSingletonArray(0); - - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - - for (uint256 i = 0; i < totalIds; ) { - uint256 id = ids[i]; - - require(_balances[account][id] == true, "EIP5516: Unauthorized"); - - delete _balances[account][id]; - - unchecked { - ++i; - } - } - - emit TransferBatch(operator, account, address(0), ids, values); - - _afterTokenTransfer(operator, account, address(0), ids, amounts, ""); - } - - /** - * @dev Approve `operator` to operate on all of `owner` tokens - * - * Emits a {ApprovalForAll} event. - * - */ - function _setApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "ERC1155: setting approval status for self"); - _operatorApprovals[owner][operator] = approved; - emit ApprovalForAll(owner, operator, approved); - } - - /** - * @dev Hook that is called before any token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `ids` and `amounts` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called after any token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called before any batched token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeBatchedTokenTransfer( - address operator, - address from, - address[] memory to, - uint256 id, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called after any batched token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterBatchedTokenTransfer( - address operator, - address from, - address[] memory to, - uint256 id, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called before any token claim. - + - * Calling conditions (for each `action` and `id` pair): - * - * - A token under `id` must exist. - * - When `action` is non-zero, a token under `id` will now be claimed and owned by`operator`. - * - When `action` is false, a token under `id` will now be rejected. - * - */ - function _beforeTokenClaim( - address operator, - address account, - bool[] memory actions, - uint256[] memory ids - ) internal virtual {} - - /** - * @dev Hook that is called after any token claim. - + - * Calling conditions (for each `action` and `id` pair): - * - * - A token under `id` must exist. - * - When `action` is non-zero, a token under `id` is now owned by`operator`. - * - When `action` is false, a token under `id` was rejected. - * - */ - function _afterTokenClaim( - address operator, - address account, - bool[] memory actions, - uint256[] memory ids - ) internal virtual {} - - function _asSingletonArray(uint256 element) - private - pure - returns (uint256[] memory) - { - uint256[] memory array = new uint256[](1); - array[0] = element; - - return array; - } - - /** - * @dev see {ERC1155-_doSafeTransferAcceptanceCheck, IERC1155Receivable} - */ - function _doSafeTransferAcceptanceCheck( - address operator, - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) private { - if (to.isContract()) { - try - IERC1155Receiver(to).onERC1155Received( - operator, - from, - id, - amount, - data - ) - returns (bytes4 response) { - if (response != IERC1155Receiver.onERC1155Received.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); - } - } catch Error(string memory reason) { - revert(reason); - } catch { - revert("ERC1155: transfer to non-ERC1155Receiver implementer"); - } - } - } - - /** - * @dev Unused/Deprecated function - * @dev See {IERC1155-safeBatchTransferFrom}. - */ - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public virtual override {} -} diff --git a/assets/eip-5516/IERC5516.sol b/assets/eip-5516/IERC5516.sol deleted file mode 100644 index a827f95..0000000 --- a/assets/eip-5516/IERC5516.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.4; - -/** - @title Soulbound, Multi-Token standard. - @notice Interface of the EIP-5516 - Note: The ERC-165 identifier for this interface is 0x8314f22b. - */ - -interface IERC5516 { - /** - * @dev Emitted when `account` claims or rejects pending tokens under `ids[]`. - */ - event TokenClaimed( - address indexed operator, - address indexed account, - bool[] actions, - uint256[] ids - ); - - /** - * @dev Emitted when `from` transfers token under `id` to every address at `to[]`. - */ - event TransferMulti( - address indexed operator, - address indexed from, - address[] to, - uint256 amount, - uint256 id - ); - - /** - * @dev Get tokens owned by a given address. - */ - function tokensFrom(address from) external view returns (uint256[] memory); - - /** - * @dev Get tokens awaiting to be claimed by a given address. - */ - function pendingFrom(address from) external view returns (uint256[] memory); - - /** - * @dev Claims or Reject pending `id`. - * - * Requirements: - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function claimOrReject( - address account, - uint256 id, - bool action - ) external; - - /** - * @dev Claims or Reject pending tokens under `ids[]`. - * - * Requirements for each `id` `action` pair: - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function claimOrRejectBatch( - address account, - uint256[] memory ids, - bool[] memory actions - ) external; - - /** - * @dev Transfers `id` token from `from` to every address at `to[]`. - * - * Requirements: - * - * - `from` MUST be the creator(minter) of `id`. - * - All addresses in `to[]` MUST be non-zero. - * - All addresses in `to[]` MUST have the token `id` under `_pendings`. - * - All addresses in `to[]` MUST not own a token type under `id`. - * - * Emits a {TransfersMulti} event. - * - */ - function batchTransfer( - address from, - address[] memory to, - uint256 id, - uint256 amount, - bytes memory data - ) external; - -} diff --git a/assets/eip-5528/ERC20Mockup.sol b/assets/eip-5528/ERC20Mockup.sol deleted file mode 100644 index d5dd437..0000000 --- a/assets/eip-5528/ERC20Mockup.sol +++ /dev/null @@ -1,55 +0,0 @@ -pragma solidity ^0.4.24; - - - - -contract ERC20Mockup { - mapping(address => uint256) _balances; - uint256 _totalSupply; - address _owner; - - constructor(address initialAccount, uint256 initialBalance) { - _owner = initialAccount; - _totalSupply = initialBalance; - _balances[initialAccount] = initialBalance; - } - - function balanceOf(address account) public view returns (uint256) { - return _balances[account]; - } - function transfer(address to, uint256 amount) public returns (bool) { - address owner = msg.sender; - _transfer(owner, to, amount); - return true; - } - - function _transfer( - address from, - address to, - uint256 amount - ) internal { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - _balances[from] = fromBalance - amount; - _balances[to] += amount; - } - /* - From there, escrow related function - */ - function escrowFund(address to, uint256 amount) public returns (bool) { - bool res = ERC20Mockup(to).escrowFund(msg.sender, amount); - require(res, "Fund Failed"); - _transfer(msg.sender, to, amount); - - return true; - } - function escrowRefund(address to, uint256 amount) public returns (bool) { - bool res = ERC20Mockup(to).escrowRefund(msg.sender, amount); - require(res, "Refund Failed"); - _transfer(to, msg.sender, amount); - return true; - } -} diff --git a/assets/eip-5528/EscrowContractAccount.sol b/assets/eip-5528/EscrowContractAccount.sol deleted file mode 100644 index 99ae11b..0000000 --- a/assets/eip-5528/EscrowContractAccount.sol +++ /dev/null @@ -1,171 +0,0 @@ -pragma solidity ^0.4.24; - -import "./ERC20Mockup.sol"; - - -contract ErcEscrowAccount { - struct BalanceData { - uint256 seller; - uint256 buyer; - } - - enum State { Inited, Running, Success, Failed } - - struct EscrowStatus { - uint256 numberOfBuyer; - uint256 fundTotal; - uint256 fundFilled; - State state; - } - mapping(address => BalanceData) _balances; - - address _addrSeller; - address _addrBuyer; - address _addrEscrow; - address _addrCreator; - - EscrowStatus _status; - - constructor(uint256 fundAmount, address sellerContract, address buyerContract) { - - //require(sellerContract.code.length > 0, "seller is not contract"); - //require(buyerContract.code.length > 0, "buyer is not contract"); - - _addrBuyer = buyerContract; - _addrSeller = sellerContract; - - _status.numberOfBuyer = 0; - _status.fundTotal = fundAmount; - _status.fundFilled = 0; - - _addrEscrow = address(this); - _addrCreator = msg.sender; - _status.state = State.Inited; - } - - - function helper_bigInt256(uint256 _u256Val) public view returns (uint256) { - return _u256Val; - } - - function helper_numberOfBuyers() public view returns (uint256) { - return _status.numberOfBuyer; - } - - function _updateRunningState() { - if(_status.state == State.Running){ - if(_status.numberOfBuyer == 2){ - _status.state = State.Success; - } - } - } - - function escrowStatus() public view returns (string) { - if(_status.state == State.Inited){ - return "init"; - }else if(_status.state == State.Running){ - return "Running"; - }else if(_status.state == State.Success){ - return "Success"; - }else if(_status.state == State.Failed){ - return "Failed"; - } - return "unknown state"; - } - - - function balanceOf(address account) public view returns (uint256) { - return _balances[account].buyer; - } - - function escrowBalanceOf(address account) public view returns (uint256 o_buyer, uint256 o_seller) { - o_buyer = _balances[account].buyer; - o_seller = _balances[account].seller; - } - - function escrowFund(address to, uint256 amount) public returns (bool) { - require(amount > 0, "amount is too small"); - if(msg.sender == _addrSeller){ - - require(_status.state == State.Inited, "must be init state"); - require(to == _addrCreator, "to is only with creator"); - require(amount == _status.fundTotal, "amount must be total fund"); - require(_status.fundFilled == 0, "fund filled must be zero"); - - _status.fundFilled = amount; - - _balances[to].seller = _balances[to].seller + amount; - _balances[to].buyer = 0; - _status.state = State.Running; - - }else if(msg.sender == _addrBuyer){ - require(_status.state == State.Running, "must be running state"); - require(_status.fundTotal > 0, "escrow might be not started or already finished"); - require(_status.fundFilled == _status.fundTotal, "fund does not filled yet"); - - // TODO: this logic is only for 1:1 exchange rate - require(amount <= _balances[_addrCreator].seller, "no more token left to exchange"); - - _balances[_addrCreator].seller = _balances[_addrCreator].seller - amount; - _balances[_addrCreator].buyer = _balances[_addrCreator].buyer + amount; - - if(_balances[to].seller == 0){ - _status.numberOfBuyer = _status.numberOfBuyer + 1; - } - _balances[to].seller = _balances[to].seller + amount; - _balances[to].buyer = _balances[to].buyer + amount; - - _updateRunningState(); - }else{ - require(false, "Todo other cases"); - } - - - - return true; - } - - function escrowRefund(address to, uint256 amount) public returns (bool) { - require(amount > 0, "amount is too small"); - require(_status.state == State.Running || _status.state == State.Failed, "must be running state to refund"); - require(msg.sender == _addrBuyer, "must be buyer contract to refund"); - require(_balances[to].buyer >= amount, "buyer fund is not enough to refund"); - - - _balances[to].buyer = _balances[to].buyer - amount; - _balances[to].seller = _balances[to].seller - amount; - - _balances[_addrCreator].seller = _balances[_addrCreator].seller + amount; - _balances[_addrCreator].buyer = _balances[_addrCreator].buyer - amount; - - if(_balances[to].buyer == 0){ - _status.numberOfBuyer = _status.numberOfBuyer - 1; - } - - _updateRunningState(); - return true; - } - - function escrowWithdraw() public returns (bool) { - address from = msg.sender; - - if(from == _addrCreator){ - if(_status.state == State.Success){ - ERC20Mockup(_addrBuyer).transfer(from, _balances[from].buyer); - ERC20Mockup(_addrSeller).transfer(from, _balances[from].seller); - - }else if(_status.state == State.Failed){ - ERC20Mockup(_addrSeller).transfer(from, _status.fundFilled); - }else{ - require(false, "invalid state for seller withdraw"); - } - }else{ - require(_status.state == State.Success, "withdraw is only in success, otherwise use refund"); - ERC20Mockup(_addrSeller).transfer(from, _balances[from].seller); - } - - delete _balances[from]; - return true; - } - -} diff --git a/assets/eip-5528/truffule-test.js b/assets/eip-5528/truffule-test.js deleted file mode 100644 index 3247e8e..0000000 --- a/assets/eip-5528/truffule-test.js +++ /dev/null @@ -1,115 +0,0 @@ -const EscrowContractAccount = artifacts.require('./EscrowContractAccount') -const ERC20Mockup = artifacts.require('./ERC20Mockup') - -const util = require('util') -contract('ERCEscrowMockup', accounts => { - const [userCreator, userSeller, userBuyer01, userBuyer02, ...others] = accounts - - let contracts - - const BNConst = { - totalFund: 100, - user1: 10, - user2: 33, - zero: 0, - DigOne: 1, - DigTwo: 2, - } - - before(async () => { - const seller = await ERC20Mockup.new(userCreator, 10000) - await seller.transfer(userSeller, 1000, {from: userCreator}) - - const buyer = await ERC20Mockup.new(userCreator, 10000) - await buyer.transfer(userBuyer01, 1000, {from: userCreator}) - await buyer.transfer(userBuyer02, 1000, {from: userCreator}) - - const escrow = await EscrowContractAccount.new(BNConst.totalFund, seller.address, buyer.address, {from: userSeller}) - - contracts = { - escrow, - seller, - buyer, - } - for (const key in BNConst) { - const v = BNConst[key] - BNConst[key] = { - origin: v, - bn: await escrow.helper_bigInt256(v), - } - } - //console.log('-check point-1-', util.inspect(contracts, false, null, true)) - }) - - it('escrow start', async () => { - const result = await contracts.seller.escrowFund(contracts.escrow.address, BNConst.totalFund.origin, { - from: userSeller, - }) - const [buyer, seller] = await contracts.escrow.escrowBalanceOf(userSeller) - const state = await contracts.escrow.escrowStatus() - //console.log('---check00000', state) - //console.log('-check point-1-', util.inspect(result, false, null, true)) - //console.log('--balance---', {buyer, seller, bigNumberPrefix}) - assert(buyer.eq(BNConst.zero.bn)) - assert(seller.eq(BNConst.totalFund.bn)) - }) - - it('purchase first buyer', async () => { - await contracts.buyer.escrowFund(contracts.escrow.address, BNConst.user1.origin, {from: userBuyer01}) - const [buyer, seller] = await contracts.escrow.escrowBalanceOf(userBuyer01) - }) - it('first buyer refund and purchase', async () => { - await contracts.buyer.escrowRefund(contracts.escrow.address, BNConst.user1.origin, {from: userBuyer01}) - let result = await contracts.escrow.helper_numberOfBuyers() - //console.log('-----1----', result) - assert(result.eq(BNConst.zero.bn)) - - await contracts.buyer.escrowFund(contracts.escrow.address, BNConst.user1.origin, {from: userBuyer01}) - result = await contracts.escrow.helper_numberOfBuyers() - //console.log('-----1----', result) - assert(result.eq(BNConst.DigOne.bn)) - }) - it('send buyer purchase can finialize fund', async () => { - await contracts.buyer.escrowFund(contracts.escrow.address, BNConst.user2.origin, {from: userBuyer02}) - let result = await contracts.escrow.helper_numberOfBuyers() - //console.log('-----1----', result) - assert(result.eq(BNConst.DigTwo.bn)) - result = await contracts.escrow.escrowStatus() - //console.log('-----2----', result) - assert(result === 'Success') - }) - it('check balance of seller and buyer', async () => { - const balance = { - sellerToken: { - issuer: await contracts.seller.balanceOf(userSeller), - b01: await contracts.seller.balanceOf(userBuyer01), - b02: await contracts.seller.balanceOf(userBuyer02), - }, - buyerToken: { - issuer: await contracts.buyer.balanceOf(userSeller), - b01: await contracts.buyer.balanceOf(userBuyer01), - b02: await contracts.buyer.balanceOf(userBuyer02), - }, - } - //console.log('-check point-1-', util.inspect(balance, false, null, true)) - }) - - it('check balance after withdraw', async () => { - await contracts.escrow.escrowWithdraw({from: userSeller}) - await contracts.escrow.escrowWithdraw({from: userBuyer01}) - await contracts.escrow.escrowWithdraw({from: userBuyer02}) - const balance = { - sellerToken: { - issuer: await contracts.seller.balanceOf(userSeller), - b01: await contracts.seller.balanceOf(userBuyer01), - b02: await contracts.seller.balanceOf(userBuyer02), - }, - buyerToken: { - issuer: await contracts.buyer.balanceOf(userSeller), - b01: await contracts.buyer.balanceOf(userBuyer01), - b02: await contracts.buyer.balanceOf(userBuyer02), - }, - } - //console.log('-check point-1-', util.inspect(balance, false, null, true)) - }) -}) diff --git a/assets/eip-5564/minimal_poc.ipynb b/assets/eip-5564/minimal_poc.ipynb deleted file mode 100644 index c7e589d..0000000 --- a/assets/eip-5564/minimal_poc.ipynb +++ /dev/null @@ -1,639 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "d2c6b4cd", - "metadata": {}, - "outputs": [], - "source": [ - "# PoC using scaning and spending keys" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "6bcea120", - "metadata": {}, - "outputs": [], - "source": [ - "import hashlib\n", - "from py_ecc.secp256k1 import *\n", - "import sha3\n", - "from eth_account import Account" - ] - }, - { - "cell_type": "markdown", - "id": "4e25cb04", - "metadata": {}, - "source": [ - "## Sender" - ] - }, - { - "cell_type": "markdown", - "id": "22ca0bf7", - "metadata": {}, - "source": [ - "$S = G*s$" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "bb9355a0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(22246744184454969143801186698733154500632648736073949898323976612504587645286,\n", - " 110772761940586493986212935445517909380300793379795289150161960681985511655321)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# privkey: 0xd952fe0740d9d14011fc8ead3ab7de3c739d3aa93ce9254c10b0134d80d26a30\n", - "# address: 0x3CB39EA2f14B16B69B451719A7BEd55e0aFEcE8F\n", - "s = int(0xd952fe0740d9d14011fc8ead3ab7de3c739d3aa93ce9254c10b0134d80d26a30) # private key\n", - "S = secp256k1.privtopub(s.to_bytes(32, \"big\")) # public key\n", - "S" - ] - }, - { - "cell_type": "markdown", - "id": "c8240f67", - "metadata": {}, - "source": [ - "## Recipient" - ] - }, - { - "cell_type": "markdown", - "id": "6895e603", - "metadata": {}, - "source": [ - "$P = G*p$" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "c8e2d6ad", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "((89565891926547004231252920425935692360644145829622209833684329913297188986597,\n", - " 12158399299693830322967808612713398636155367887041628176798871954788371653930),\n", - " (112711660439710606056748659173929673102114977341539408544630613555209775888121,\n", - " 25583027980570883691656905877401976406448868254816295069919888960541586679410))" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# privkey: 0x0000000000000000000000000000000000000000000000000000000000000001\n", - "# address: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf\n", - "p_scan = int(0x0000000000000000000000000000000000000000000000000000000000000002) # private key\n", - "p_spend = int(0x0000000000000000000000000000000000000000000000000000000000000003) # private key\n", - "\n", - "P_scan = secp256k1.privtopub(p_scan.to_bytes(32, \"big\")) # public key\n", - "P_spend = secp256k1.privtopub(p_spend.to_bytes(32, \"big\")) # public key\n", - "P_scan, P_spend" - ] - }, - { - "cell_type": "markdown", - "id": "174929d7", - "metadata": {}, - "source": [ - "## Calculate Stealth Address: $P_{spend} + G*hash(Q)$" - ] - }, - { - "cell_type": "markdown", - "id": "8b39ed39", - "metadata": {}, - "source": [ - "$Q = S * p_{scan}$" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "63a022d7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(65311808848028536848162101908966111079795231803322390815513763038079235257196,\n", - " 43767810034999830518515787564234053904327508763526333662117780420755425490082)" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q = secp256k1.multiply(P_scan, s)\n", - "Q" - ] - }, - { - "cell_type": "markdown", - "id": "d79c69fc", - "metadata": {}, - "source": [ - "$Q = S * p_{scan} = P_{scan} * s$" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "5f5fbcf4", - "metadata": {}, - "outputs": [], - "source": [ - "assert Q == secp256k1.multiply(S, p_scan)" - ] - }, - { - "cell_type": "markdown", - "id": "0d5803ff", - "metadata": {}, - "source": [ - "$h(Q)$" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f1b38cb0", - "metadata": {}, - "outputs": [], - "source": [ - "Q_hex = sha3.keccak_256(Q[0].to_bytes(32, \"big\") \n", - " + Q[1].to_bytes(32, \"big\")\n", - " ).hexdigest()\n", - "Q_hased = bytearray.fromhex(Q_hex)" - ] - }, - { - "cell_type": "markdown", - "id": "a0647821", - "metadata": {}, - "source": [ - "$ stA = h(Q) * G + P_{spend}$" - ] - }, - { - "cell_type": "markdown", - "id": "865e7f72", - "metadata": {}, - "source": [ - "#### Sender sends funds to..." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "d9dd755f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "stP = secp256k1.add(P_spend, secp256k1.privtopub(Q_hased))\n", - "stA = \"0x\"+ sha3.keccak_256(stP[0].to_bytes(32, \"big\")\n", - " +stP[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "stA" - ] - }, - { - "cell_type": "markdown", - "id": "38e69080", - "metadata": {}, - "source": [ - "#### Sender broadcasts" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cdf57fef", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "((22246744184454969143801186698733154500632648736073949898323976612504587645286,\n", - " 110772761940586493986212935445517909380300793379795289150161960681985511655321),\n", - " '0xfed69df0a27f1dae0d7430ead82aaedfad6332bb')" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "S, stA" - ] - }, - { - "cell_type": "markdown", - "id": "588ccc7c", - "metadata": {}, - "source": [ - "## Parse received funds" - ] - }, - { - "cell_type": "markdown", - "id": "462f8c8d", - "metadata": {}, - "source": [ - "* Note that $p_{scan}$ and $P_{spend}$ can be shared with a trusted party\n", - "* There may be many S to be parsed" - ] - }, - { - "cell_type": "markdown", - "id": "8ba2a295", - "metadata": {}, - "source": [ - "$h(p_{scan}*S)*G + P_{spend} => toAddress$" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "50b63208", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q = secp256k1.multiply(S, p_scan)\n", - "Q_hex = sha3.keccak_256(Q[0].to_bytes(32, \"big\")+Q[1].to_bytes(32, \"big\")).hexdigest()\n", - "Q_hased = bytearray.fromhex(Q_hex)\n", - "\n", - "P_stealth = secp256k1.add(P_spend, secp256k1.privtopub(Q_hased))\n", - "P_stealthAddress = \"0x\"+ sha3.keccak_256(stP[0].to_bytes(32, \"big\")\n", - " + stP[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "P_stealthAddress" - ] - }, - { - "cell_type": "markdown", - "id": "8055d075", - "metadata": {}, - "source": [ - "logged stealth address $stA$ equals the derived stealth address $P_stealthAddress$" - ] - }, - { - "cell_type": "markdown", - "id": "26758ea5", - "metadata": {}, - "source": [ - "$stA==stA_d$" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "3faed6a3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "P_stealthAddress == stA" - ] - }, - { - "cell_type": "markdown", - "id": "050e346c", - "metadata": {}, - "source": [ - "## Derive private key" - ] - }, - { - "cell_type": "markdown", - "id": "44801516", - "metadata": {}, - "source": [ - "#### Only the recipient has access to $p_{spend}$" - ] - }, - { - "cell_type": "markdown", - "id": "7673e439", - "metadata": {}, - "source": [ - "$p_{stealth}=p_{spend}+hash(Q)$" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "4013b57e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "39153944482575822531387237249775711740128993925789544779866399859639729033274" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q = secp256k1.multiply(S, p_scan)\n", - "Q_hex = sha3.keccak_256(Q[0].to_bytes(32, \"big\")+Q[1].to_bytes(32, \"big\")).hexdigest()\n", - "p_stealth = p_spend + int(Q_hex, 16)\n", - "p_stealth" - ] - }, - { - "cell_type": "markdown", - "id": "dc31c1aa", - "metadata": {}, - "source": [ - "$P_{stealth} = p_{stealth}*G$" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "09b5ccc2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(67663851387124608323744162645277269585638670865381831245083336172545348387042,\n", - " 80449904826544093817252981338261706033086352950841917067356875711772573870404)" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Recipient has private key to ...\n", - "P_stealth = secp256k1.privtopub(p_stealth.to_bytes(32, \"big\"))\n", - "P_stealth" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "a3ead30e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "P_stealthAddress_d = \"0x\"+ sha3.keccak_256(P_stealth[0].to_bytes(32, \"big\")\n", - " + P_stealth[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "P_stealthAddress_d" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "2712c07b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfEd69Df0a27F1daE0D7430EAd82aaEdfAD6332bb'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Account.from_key((p_stealth).to_bytes(32, \"big\")).address" - ] - }, - { - "cell_type": "markdown", - "id": "74f0325e", - "metadata": {}, - "source": [ - "## Additionally add view tags" - ] - }, - { - "cell_type": "markdown", - "id": "ac45bb87", - "metadata": {}, - "source": [ - "In addition to S and stA, the sender also broadcasts the first byte of h(Q)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "9645b880", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "86" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q_hased[0]" - ] - }, - { - "cell_type": "markdown", - "id": "8788f2f5", - "metadata": {}, - "source": [ - "The recipient can do the the same a before without one EC Multiplication, one EC Addition and on Public Key to Address Conversion in order to check being a potential recipient." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "bb9f5852", - "metadata": {}, - "outputs": [], - "source": [ - "Q_derived = secp256k1.multiply(S, p_scan)\n", - "Q_hex_derived = sha3.keccak_256(Q_derived[0].to_bytes(32, \"big\")\n", - " +Q_derived[1].to_bytes(32, \"big\")\n", - " ).hexdigest()\n", - "Q_hashed_derived = bytearray.fromhex(Q_hex_derived)" - ] - }, - { - "cell_type": "markdown", - "id": "f7dc4624", - "metadata": {}, - "source": [ - "Check view tag" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "953bf07d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run = Q_hased[0] == Q_hashed_derived[0] \n", - "run" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "e11ec134", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "if run:\n", - " P_stealth = secp256k1.add(P_spend, secp256k1.privtopub(Q_hased))\n", - " P_stealthAddress = \"0x\"+ sha3.keccak_256(stP[0].to_bytes(32, \"big\")\n", - " + stP[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "P_stealthAddress" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "bd06ffc5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "P_stealthAddress==stA" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "hackathon", - "language": "python", - "name": "hackathon" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/assets/eip-5564/scheme_ids.md b/assets/eip-5564/scheme_ids.md deleted file mode 100644 index 090da4a..0000000 --- a/assets/eip-5564/scheme_ids.md +++ /dev/null @@ -1,8 +0,0 @@ -# EIP-5564 - Scheme Id Mapping - - -Last edited 07.02.2023 - -| ID | Scheme | EIP | Added | -|---|---|---|---| -| 0 | SECP256k1, with view tags. | [EIP-5564](https://eips.ethereum.org/EIPS/eip-5564) | 07.02.2023 | diff --git a/assets/eip-5606/contracts/MultiverseNFT.sol b/assets/eip-5606/contracts/MultiverseNFT.sol deleted file mode 100644 index b250619..0000000 --- a/assets/eip-5606/contracts/MultiverseNFT.sol +++ /dev/null @@ -1,421 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; - -contract ERC721Full is ERC721Enumerable, ERC721URIStorage { - /// @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. - /// @param name is a non-empty string - /// @param symbol is a non-empty string - constructor(string memory name, string memory symbol) - ERC721(name, symbol) - {} - - /// @dev Hook that is called before any token transfer. This includes minting and burning. `from`'s `tokenId` will be transferred to `to` - /// @param from is an non-zero address - /// @param to is an non-zero address - /// @param tokenId is an uint256 which determine token transferred from `from` to `to` - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override(ERC721Enumerable, ERC721) { - ERC721Enumerable._beforeTokenTransfer(from, to, tokenId); - } - - /// @notice Interface of the ERC165 standard - /// @param interfaceId is a byte4 which determine interface used - /// @return true if this contract implements the interface defined by `interfaceId` - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721Enumerable, ERC721) - returns (bool) - { - return - ERC721.supportsInterface(interfaceId) || - ERC721Enumerable.supportsInterface(interfaceId); - } - - /// @notice the Uniform Resource Identifier (URI) for `tokenId` token - /// @param tokenId is unit256 - /// @return string of (URI) for `tokenId` token - function tokenURI(uint256 tokenId) - public - view - virtual - override(ERC721URIStorage, ERC721) - returns (string memory) - { - return ERC721URIStorage.tokenURI(tokenId); - } - - function _burn(uint256 tokenId) - internal - override(ERC721, ERC721URIStorage) - {} -} - -/** - * @dev Interface of the Multiverse NFT standard as defined in the EIP. - */ -interface IMultiverseNFT { - /** - * @dev struct to store delegate token details - * - */ - struct DelegateData { - address contractAddress; - uint256 tokenId; - uint256 quantity; - } - - /** - * @dev Emitted when one or more new delegate NFTs are added to a Multiverse NFT - * - */ - event Bundled( - uint256 multiverseTokenID, - DelegateData[] delegateData, - address ownerAddress - ); - - /** - * @dev Emitted when one or more delegate NFTs are removed from a Multiverse NFT - */ - event Unbundled(uint256 multiverseTokenID, DelegateData[] delegateData); - - /** - * @dev Accepts the tokenId of the Multiverse NFT and returns an array of delegate token data - */ - function delegateTokens(uint256 multiverseTokenID) - external - view - returns (DelegateData[] memory); - - /** - * @dev Removes one or more delegate NFTs from a Multiverse NFT - * This function accepts the delegate NFT details, and transfer those NFTs out of the Multiverse NFT contract to the owner's wallet - */ - function unbundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external; - - /** - * @dev Adds one or more delegate NFTs to a Multiverse NFT - * This function accepts the delegate NFT details, and transfers those NFTs to the Multiverse NFT contract - * Need to ensure that approval is given to this Multiverse NFT contract for the delegate NFTs so that they can be transferred programmatically - */ - function bundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external; - - /** - * @dev Initializes a new bundle, mints a Multiverse NFT and assigns it to msg.sender - * Returns the token ID of a new Multiverse NFT - * Note - When a new Multiverse NFT is initialized, it is empty, it does not contain any delegate NFTs - */ - function initBundle(DelegateData[] memory delegateData) external; -} - -abstract contract MultiverseNFT is - IMultiverseNFT, - Ownable, - ERC721Full, - IERC1155Receiver, - AccessControl -{ - using SafeMath for uint256; - bytes32 public constant BUNDLER_ROLE = keccak256("BUNDLER_ROLE"); - - uint256 currentMultiverseTokenID; - - mapping(uint256 => DelegateData[]) public multiverseNFTDelegateData; - mapping(uint256 => mapping(address => mapping(uint256 => uint256))) - public tokenBalances; - - constructor(address bundlerAddress) { - _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); - _setupRole(BUNDLER_ROLE, msg.sender); - _setRoleAdmin(BUNDLER_ROLE, DEFAULT_ADMIN_ROLE); - _setupRole(BUNDLER_ROLE, bundlerAddress); - } - - function delegateTokens(uint256 multiverseTokenID) - external - view - returns (DelegateData[] memory) - { - return multiverseNFTDelegateData[multiverseTokenID]; - } - - function initBundle(DelegateData[] memory delegateData) external { - uint256 tokenId = currentMultiverseTokenID.add(1); - for (uint256 i = 0; i < delegateData.length; i = i.add(1)) { - bool isERC721 = _isERC721(delegateData[i].contractAddress); - if (isERC721) { - require( - delegateData[i].quantity == 1, - "ERC721 quantity must be 1" - ); - } - multiverseNFTDelegateData[tokenId].push(delegateData[i]); - } - - _incrementMultiverseTokenID(); - _safeMint(msg.sender, tokenId); - } - - function bundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external { - require( - hasRole(BUNDLER_ROLE, msg.sender) || - ownerOf(multiverseTokenID) == msg.sender, - "msg.sender neither have bundler role nor multiversetoken owner" - ); - _bundle(delegateData, multiverseTokenID); - } - - function unbundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external { - require( - ownerOf(multiverseTokenID) == msg.sender, - "msg.sender is not a multiversetoken owner" - ); - for (uint256 i = 0; i < delegateData.length; i = i.add(1)) { - require( - _ensureDelegateBelongsToMultiverseNFT( - delegateData[i], - multiverseTokenID - ), - "delegate not assigned to multiverse token" - ); - uint256 balance = tokenBalances[multiverseTokenID][ - delegateData[i].contractAddress - ][delegateData[i].tokenId]; - require( - delegateData[i].quantity <= balance, - "quantity exceeds balance" - ); - require( - _ensureMultiverseContractOwnsDelegate(delegateData[i]), - "delegate not owned by contract" - ); - - address contractAddress = delegateData[i].contractAddress; - uint256 tokenId = delegateData[i].tokenId; - uint256 quantity = delegateData[i].quantity; - - _updateDelegateBalances(delegateData[i], multiverseTokenID); - - if (_isERC721(contractAddress)) { - ERC721Full erc721Instance = ERC721Full(contractAddress); - erc721Instance.transferFrom(address(this), msg.sender, tokenId); - } else if (_isERC1155(contractAddress)) { - ERC1155Supply erc1155Instance = ERC1155Supply(contractAddress); - erc1155Instance.safeTransferFrom( - address(this), - msg.sender, - tokenId, - quantity, - "" - ); - } - } - emit Unbundled(multiverseTokenID, delegateData); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(AccessControl, ERC721Full, IERC165) - returns (bool) - { - return - AccessControl.supportsInterface(interfaceId) || - ERC721Full.supportsInterface(interfaceId); - } - - function _bundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) internal { - for (uint256 i = 0; i < delegateData.length; i = i.add(1)) { - require( - _ensureDelegateBelongsToMultiverseNFT( - delegateData[i], - multiverseTokenID - ), - "delegate not assigned to multiversetoken" - ); - require( - _ensureDelegateQuantityLimitForMMultiverseNFT( - delegateData[i], - multiverseTokenID - ), - "delegate quantity assigned to multiversetoken exceeds" - ); - - address contractAddress = delegateData[i].contractAddress; - uint256 tokenId = delegateData[i].tokenId; - uint256 quantity = delegateData[i].quantity; - - tokenBalances[multiverseTokenID][contractAddress][ - tokenId - ] = tokenBalances[multiverseTokenID][contractAddress][tokenId].add( - quantity - ); - - if (_isERC721(contractAddress)) { - require( - quantity == 1, - "ERC721 cannot have quantity more than 1" - ); - ERC721Full erc721Instance = ERC721Full(contractAddress); - erc721Instance.transferFrom(msg.sender, address(this), tokenId); - } else if (_isERC1155(contractAddress)) { - ERC1155Supply erc1155Instance = ERC1155Supply(contractAddress); - erc1155Instance.safeTransferFrom( - msg.sender, - address(this), - tokenId, - quantity, - "" - ); - } - } - emit Bundled( - multiverseTokenID, - delegateData, - ownerOf(multiverseTokenID) - ); - } - - function _ensureDelegateBelongsToMultiverseNFT( - DelegateData memory delegateData, - uint256 multiverseTokenID - ) internal view returns (bool) { - DelegateData[] memory storedData = multiverseNFTDelegateData[ - multiverseTokenID - ]; - for (uint256 i = 0; i < storedData.length; i = i.add(1)) { - if ( - delegateData.contractAddress == storedData[i].contractAddress && - delegateData.tokenId == storedData[i].tokenId - ) { - return true; - } - } - return false; - } - - function _ensureMultiverseContractOwnsDelegate( - DelegateData memory delegateData - ) internal view returns (bool) { - if (_isERC721(delegateData.contractAddress)) { - ERC721Full erc721Instance = ERC721Full( - delegateData.contractAddress - ); - if (address(this) == erc721Instance.ownerOf(delegateData.tokenId)) { - return true; - } - } else if (_isERC1155(delegateData.contractAddress)) { - ERC1155Supply erc1155Instance = ERC1155Supply( - delegateData.contractAddress - ); - if ( - erc1155Instance.balanceOf( - address(this), - delegateData.tokenId - ) >= delegateData.quantity - ) { - return true; - } - } - return false; - } - - function _ensureDelegateQuantityLimitForMMultiverseNFT( - DelegateData memory delegateData, - uint256 multiverseTokenID - ) internal view returns (bool) { - DelegateData[] memory storedData = multiverseNFTDelegateData[ - multiverseTokenID - ]; - for (uint256 i = 0; i < storedData.length; i = i.add(1)) { - if ( - delegateData.contractAddress == storedData[i].contractAddress && - delegateData.tokenId == storedData[i].tokenId - ) { - uint256 balance = tokenBalances[multiverseTokenID][ - delegateData.contractAddress - ][delegateData.tokenId]; - if ( - balance.add(delegateData.quantity) <= storedData[i].quantity - ) { - return true; - } - return false; - } - } - } - - function _updateDelegateBalances( - DelegateData memory delegateData, - uint256 multiverseTokenID - ) internal returns (uint256) { - address contractAddress = delegateData.contractAddress; - uint256 tokenId = delegateData.tokenId; - tokenBalances[multiverseTokenID][contractAddress][ - tokenId - ] = tokenBalances[multiverseTokenID][contractAddress][tokenId].sub( - delegateData.quantity - ); - return tokenBalances[multiverseTokenID][contractAddress][tokenId]; - } - - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external pure override returns (bytes4) { - return this.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external pure override returns (bytes4) { - return this.onERC1155BatchReceived.selector; - } - - function _isERC1155(address contractAddress) internal view returns (bool) { - return IERC1155(contractAddress).supportsInterface(0xd9b67a26); - } - - function _isERC721(address contractAddress) internal view returns (bool) { - return IERC721(contractAddress).supportsInterface(0x80ac58cd); - } - - function _incrementMultiverseTokenID() internal { - currentMultiverseTokenID = currentMultiverseTokenID.add(1); - } -} diff --git a/assets/eip-5633/contracts/ERC5633.sol b/assets/eip-5633/contracts/ERC5633.sol deleted file mode 100644 index 121f6bf..0000000 --- a/assets/eip-5633/contracts/ERC5633.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "./IERC5633.sol"; - -/** - * @dev Extension of ERC1155 that adds soulbound property per token id. - * - */ -abstract contract ERC5633 is ERC1155, IERC5633 { - mapping(uint256 => bool) private _soulbounds; - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155) returns (bool) { - return interfaceId == type(IERC5633).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * @dev Returns true if a token type `id` is soulbound. - */ - function isSoulbound(uint256 id) public view virtual returns (bool) { - return _soulbounds[id]; - } - - function _setSoulbound(uint256 id, bool soulbound) internal { - _soulbounds[id] = soulbound; - emit Soulbound(id, soulbound); - } - - /** - * @dev See {ERC1155-_beforeTokenTransfer}. - */ - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual override { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - - for (uint256 i = 0; i < ids.length; ++i) { - if (isSoulbound(ids[i])) { - require( - from == address(0) || to == address(0), - "ERC5633: Soulbound, Non-Transferable" - ); - } - } - } -} \ No newline at end of file diff --git a/assets/eip-5633/contracts/ERC5633Demo.sol b/assets/eip-5633/contracts/ERC5633Demo.sol deleted file mode 100644 index 8427d37..0000000 --- a/assets/eip-5633/contracts/ERC5633Demo.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -import "./ERC5633.sol"; - -contract ERC5633Demo is ERC1155, ERC1155Burnable, Ownable, ERC5633 { - constructor() ERC1155("") ERC5633() {} - - function mint(address account, uint256 id, uint256 amount, bytes memory data) - public - onlyOwner - { - _mint(account, id, amount, data); - } - - function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) - public - onlyOwner - { - _mintBatch(to, ids, amounts, data); - } - - function setSoulbound(uint256 id, bool soulbound) - public - onlyOwner - { - _setSoulbound(id, soulbound); - } - - // The following functions are overrides required by Solidity. - function supportsInterface(bytes4 interfaceId) - public - view - override(ERC1155, ERC5633) - returns (bool) - { - return super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) - internal - override(ERC1155, ERC5633) - { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - } - - function getInterfaceId() public view returns (bytes4) { - return type(IERC5633).interfaceId; - } -} diff --git a/assets/eip-5633/contracts/IERC5633.sol b/assets/eip-5633/contracts/IERC5633.sol deleted file mode 100644 index 3496ea5..0000000 --- a/assets/eip-5633/contracts/IERC5633.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5633 { - /** - * @dev Emitted when a token type `id` is set or cancel to soulbound, according to `bounded`. - */ - event Soulbound(uint256 indexed id, bool bounded); - - /** - * @dev Returns true if a token type `id` is soulbound. - */ - function isSoulbound(uint256 id) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-5633/hardhat.config.js b/assets/eip-5633/hardhat.config.js deleted file mode 100644 index 7a30b5c..0000000 --- a/assets/eip-5633/hardhat.config.js +++ /dev/null @@ -1,14 +0,0 @@ -require("@nomicfoundation/hardhat-toolbox"); - -/** @type import('hardhat/config').HardhatUserConfig */ -module.exports = { - solidity: { - version: "0.8.9", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - }, -}; diff --git a/assets/eip-5633/package.json b/assets/eip-5633/package.json deleted file mode 100644 index c3dbe0a..0000000 --- a/assets/eip-5633/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "eip-5633", - "devDependencies": { - "@ethersproject/providers": "^5.7.0", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", - "@nomicfoundation/hardhat-network-helpers": "^1.0.6", - "@nomicfoundation/hardhat-toolbox": "^1.0.2", - "@nomiclabs/hardhat-ethers": "^2.1.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/contracts": "^4.7.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.3", - "@types/mocha": "^9.1.1", - "chai": "^4.3.6", - "ethers": "^5.7.0", - "hardhat": "^2.11.0", - "hardhat-gas-reporter": "^1.0.9", - "solidity-coverage": "^0.7.22", - "ts-node": "^10.9.1", - "typechain": "^8.1.0", - "typescript": "^4.8.3" - } -} diff --git a/assets/eip-5633/test/test.js b/assets/eip-5633/test/test.js deleted file mode 100644 index 089f559..0000000 --- a/assets/eip-5633/test/test.js +++ /dev/null @@ -1,51 +0,0 @@ -const { expect } = require("chai"); -const { ethers } = require("hardhat"); - -describe("ERC5633Demo contract", function () { - - it("InterfaceId should equals 0x911ec470", async function () { - const [owner, addr1, addr2] = await ethers.getSigners(); - - const ERC5633Demo = await ethers.getContractFactory("ERC5633Demo"); - - const demo = await ERC5633Demo.deploy(); - await demo.deployed(); - - expect(await demo.getInterfaceId()).equals("0x911ec470"); - }); - - it("Test soulbound", async function () { - const [owner, addr1, addr2] = await ethers.getSigners(); - - const ERC5633Demo = await ethers.getContractFactory("ERC5633Demo"); - - const demo = await ERC5633Demo.deploy(); - await demo.deployed(); - - await demo.setSoulbound(1, true); - expect(await demo.isSoulbound(1)).to.equal(true); - expect(await demo.isSoulbound(2)).to.equal(false); - - await demo.mint(addr1.address, 1, 2, "0x"); - await demo.mint(addr1.address, 2, 2, "0x"); - - await expect(demo.connect(addr1).safeTransferFrom(addr1.address, addr2.address, 1, 1, "0x")).to.be.revertedWith( - "ERC5633: Soulbound, Non-Transferable" - ); - await expect(demo.connect(addr1).safeBatchTransferFrom(addr1.address, addr2.address, [1], [1], "0x")).to.be.revertedWith( - "ERC5633: Soulbound, Non-Transferable" - ); - await expect(demo.connect(addr1).safeBatchTransferFrom(addr1.address, addr2.address, [1,2], [1,1], "0x")).to.be.revertedWith( - "ERC5633: Soulbound, Non-Transferable" - ); - - await demo.mint(addr1.address, 2, 1, "0x"); - demo.connect(addr1).safeTransferFrom(addr1.address, addr2.address, 2, 1, "0x"); - demo.connect(addr1).safeBatchTransferFrom(addr1.address, addr2.address, [2], [1], "0x"); - - await demo.connect(addr1).burn(addr1.address, 1, 1); - await demo.connect(addr1).burnBatch(addr1.address, [1], [1]); - await demo.connect(addr2).burn(addr2.address, 2, 1); - await demo.connect(addr2).burnBatch(addr2.address, [2], [1]); - }); -}); diff --git a/assets/eip-5639/DelegationRegistry.sol b/assets/eip-5639/DelegationRegistry.sol deleted file mode 100644 index b48fa00..0000000 --- a/assets/eip-5639/DelegationRegistry.sol +++ /dev/null @@ -1,449 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IDelegationRegistry} from "./IDelegationRegistry.sol"; -import {EnumerableSet} from "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {ERC165} from "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol"; - -/** - * @title DelegationRegistry - * @custom:version 0.2 - * @notice An immutable registry contract to be deployed as a standalone primitive. - * New project launches can read previous cold wallet -> hot wallet delegations - * from here and integrate those permissions into their flow. - * @custom:coauthor foobar (0xfoobar) - * @custom:coauthor wwchung (manifoldxyz) - * @custom:coauthor purplehat (artblocks) - * @custom:coauthor ryley-o (artblocks) - * @custom:coauthor andy8052 (tessera) - * @custom:coauthor punk6529 (open metaverse) - * @custom:coauthor loopify (loopiverse) - * @custom:coauthor emiliano (nftrentals) - * @custom:coauthor arran (proof) - * @custom:coauthor james (collabland) - * @custom:coauthor john (gnosis safe) - * @custom:coauthor 0xrusowsky - */ -contract DelegationRegistry is IDelegationRegistry, ERC165 { - using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableSet for EnumerableSet.Bytes32Set; - - /// @notice The global mapping and single source of truth for delegations - /// @dev vault -> vaultVersion -> delegationHash - mapping(address => mapping(uint256 => EnumerableSet.Bytes32Set)) internal delegations; - - /// @notice A mapping of wallets to versions (for cheap revocation) - mapping(address => uint256) internal vaultVersion; - - /// @notice A mapping of wallets to delegates to versions (for cheap revocation) - mapping(address => mapping(address => uint256)) internal delegateVersion; - - /// @notice A secondary mapping to return onchain enumerability of delegations that a given address can perform - /// @dev delegate -> delegationHashes - mapping(address => EnumerableSet.Bytes32Set) internal delegationHashes; - - /// @notice A secondary mapping used to return delegation information about a delegation - /// @dev delegationHash -> DelegateInfo - mapping(bytes32 => IDelegationRegistry.DelegationInfo) internal delegationInfo; - - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) public view virtual override (ERC165) returns (bool) { - return interfaceId == type(IDelegationRegistry).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * ----------- WRITE ----------- - */ - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForAll(address delegate, bool value) external override { - bytes32 delegationHash = _computeAllDelegationHash(msg.sender, delegate); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.ALL, msg.sender, address(0), 0 - ); - emit IDelegationRegistry.DelegateForAll(msg.sender, delegate, value); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForContract(address delegate, address contract_, bool value) external override { - bytes32 delegationHash = _computeContractDelegationHash(msg.sender, delegate, contract_); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.CONTRACT, msg.sender, contract_, 0 - ); - emit IDelegationRegistry.DelegateForContract(msg.sender, delegate, contract_, value); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external override { - bytes32 delegationHash = _computeTokenDelegationHash(msg.sender, delegate, contract_, tokenId); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.TOKEN, msg.sender, contract_, tokenId - ); - emit IDelegationRegistry.DelegateForToken(msg.sender, delegate, contract_, tokenId, value); - } - - /** - * @dev Helper function to set all delegation values and enumeration sets - */ - function _setDelegationValues( - address delegate, - bytes32 delegateHash, - bool value, - IDelegationRegistry.DelegationType type_, - address vault, - address contract_, - uint256 tokenId - ) - internal - { - if (value) { - delegations[vault][vaultVersion[vault]].add(delegateHash); - delegationHashes[delegate].add(delegateHash); - delegationInfo[delegateHash] = - DelegationInfo({vault: vault, delegate: delegate, type_: type_, contract_: contract_, tokenId: tokenId}); - } else { - delegations[vault][vaultVersion[vault]].remove(delegateHash); - delegationHashes[delegate].remove(delegateHash); - delete delegationInfo[delegateHash]; - } - } - - /** - * @dev Helper function to compute delegation hash for wallet delegation - */ - function _computeAllDelegationHash(address vault, address delegate) internal view returns (bytes32) { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, vaultVersion_, delegateVersion_)); - } - - /** - * @dev Helper function to compute delegation hash for contract delegation - */ - function _computeContractDelegationHash(address vault, address delegate, address contract_) - internal - view - returns (bytes32) - { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, contract_, vaultVersion_, delegateVersion_)); - } - - /** - * @dev Helper function to compute delegation hash for token delegation - */ - function _computeTokenDelegationHash(address vault, address delegate, address contract_, uint256 tokenId) - internal - view - returns (bytes32) - { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, contract_, tokenId, vaultVersion_, delegateVersion_)); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeAllDelegates() external override { - ++vaultVersion[msg.sender]; - emit IDelegationRegistry.RevokeAllDelegates(msg.sender); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeDelegate(address delegate) external override { - _revokeDelegate(delegate, msg.sender); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeSelf(address vault) external override { - _revokeDelegate(msg.sender, vault); - } - - /** - * @dev Revoke the `delegate` hotwallet from the `vault` coldwallet. - */ - function _revokeDelegate(address delegate, address vault) internal { - ++delegateVersion[vault][delegate]; - // For enumerations, filter in the view functions - emit IDelegationRegistry.RevokeDelegate(vault, msg.sender); - } - - /** - * ----------- READ ----------- - */ - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegationsByDelegate(address delegate) - external - view - returns (IDelegationRegistry.DelegationInfo[] memory info) - { - EnumerableSet.Bytes32Set storage potentialDelegationHashes = delegationHashes[delegate]; - uint256 potentialDelegationHashesLength = potentialDelegationHashes.length(); - uint256 delegationCount = 0; - info = new IDelegationRegistry.DelegationInfo[](potentialDelegationHashesLength); - for (uint256 i = 0; i < potentialDelegationHashesLength;) { - bytes32 delegateHash = potentialDelegationHashes.at(i); - IDelegationRegistry.DelegationInfo memory delegationInfo_ = delegationInfo[delegateHash]; - address vault = delegationInfo_.vault; - IDelegationRegistry.DelegationType type_ = delegationInfo_.type_; - bool valid = false; - if (type_ == IDelegationRegistry.DelegationType.ALL) { - if (delegateHash == _computeAllDelegationHash(vault, delegate)) { - valid = true; - } - } else if (type_ == IDelegationRegistry.DelegationType.CONTRACT) { - if (delegateHash == _computeContractDelegationHash(vault, delegate, delegationInfo_.contract_)) { - valid = true; - } - } else if (type_ == IDelegationRegistry.DelegationType.TOKEN) { - if ( - delegateHash - == _computeTokenDelegationHash(vault, delegate, delegationInfo_.contract_, delegationInfo_.tokenId) - ) { - valid = true; - } - } - if (valid) { - info[delegationCount++] = delegationInfo_; - } - unchecked { - ++i; - } - } - if (potentialDelegationHashesLength > delegationCount) { - assembly { - let decrease := sub(potentialDelegationHashesLength, delegationCount) - mstore(info, sub(mload(info), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForAll(address vault) external view returns (address[] memory delegates) { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.ALL, address(0), 0); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForContract(address vault, address contract_) - external - view - override - returns (address[] memory delegates) - { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.CONTRACT, contract_, 0); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForToken(address vault, address contract_, uint256 tokenId) - external - view - override - returns (address[] memory delegates) - { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.TOKEN, contract_, tokenId); - } - - function _getDelegatesForLevel( - address vault, - IDelegationRegistry.DelegationType delegationType, - address contract_, - uint256 tokenId - ) - internal - view - returns (address[] memory delegates) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialDelegatesLength = delegationHashes_.length(); - uint256 delegatesCount = 0; - delegates = new address[](potentialDelegatesLength); - for (uint256 i = 0; i < potentialDelegatesLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == delegationType) { - if (delegationType == IDelegationRegistry.DelegationType.ALL) { - // check delegate version by validating the hash - if (delegationHash == _computeAllDelegationHash(vault, delegationInfo_.delegate)) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } else if (delegationType == IDelegationRegistry.DelegationType.CONTRACT) { - if (delegationInfo_.contract_ == contract_) { - // check delegate version by validating the hash - if ( - delegationHash == _computeContractDelegationHash(vault, delegationInfo_.delegate, contract_) - ) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } - } else if (delegationType == IDelegationRegistry.DelegationType.TOKEN) { - if (delegationInfo_.contract_ == contract_ && delegationInfo_.tokenId == tokenId) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeTokenDelegationHash(vault, delegationInfo_.delegate, contract_, tokenId) - ) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } - } - } - unchecked { - ++i; - } - } - if (potentialDelegatesLength > delegatesCount) { - assembly { - let decrease := sub(potentialDelegatesLength, delegatesCount) - mstore(delegates, sub(mload(delegates), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getContractLevelDelegations(address vault) - external - view - returns (IDelegationRegistry.ContractDelegation[] memory contractDelegations) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialLength = delegationHashes_.length(); - uint256 delegationCount = 0; - contractDelegations = new IDelegationRegistry.ContractDelegation[](potentialLength); - for (uint256 i = 0; i < potentialLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == IDelegationRegistry.DelegationType.CONTRACT) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeContractDelegationHash(vault, delegationInfo_.delegate, delegationInfo_.contract_) - ) { - contractDelegations[delegationCount++] = IDelegationRegistry.ContractDelegation({ - contract_: delegationInfo_.contract_, - delegate: delegationInfo_.delegate - }); - } - } - unchecked { - ++i; - } - } - if (potentialLength > delegationCount) { - assembly { - let decrease := sub(potentialLength, delegationCount) - mstore(contractDelegations, sub(mload(contractDelegations), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getTokenLevelDelegations(address vault) - external - view - returns (IDelegationRegistry.TokenDelegation[] memory tokenDelegations) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialLength = delegationHashes_.length(); - uint256 delegationCount = 0; - tokenDelegations = new IDelegationRegistry.TokenDelegation[](potentialLength); - for (uint256 i = 0; i < potentialLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == IDelegationRegistry.DelegationType.TOKEN) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeTokenDelegationHash( - vault, delegationInfo_.delegate, delegationInfo_.contract_, delegationInfo_.tokenId - ) - ) { - tokenDelegations[delegationCount++] = IDelegationRegistry.TokenDelegation({ - contract_: delegationInfo_.contract_, - tokenId: delegationInfo_.tokenId, - delegate: delegationInfo_.delegate - }); - } - } - unchecked { - ++i; - } - } - if (potentialLength > delegationCount) { - assembly { - let decrease := sub(potentialLength, delegationCount) - mstore(tokenDelegations, sub(mload(tokenDelegations), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForAll(address delegate, address vault) public view override returns (bool) { - bytes32 delegateHash = - keccak256(abi.encode(delegate, vault, vaultVersion[vault], delegateVersion[vault][delegate])); - return delegations[vault][vaultVersion[vault]].contains(delegateHash); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForContract(address delegate, address vault, address contract_) - public - view - override - returns (bool) - { - bytes32 delegateHash = - keccak256(abi.encode(delegate, vault, contract_, vaultVersion[vault], delegateVersion[vault][delegate])); - return - delegations[vault][vaultVersion[vault]].contains(delegateHash) - ? true - : checkDelegateForAll(delegate, vault); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId) - public - view - override - returns (bool) - { - bytes32 delegateHash = keccak256( - abi.encode(delegate, vault, contract_, tokenId, vaultVersion[vault], delegateVersion[vault][delegate]) - ); - return - delegations[vault][vaultVersion[vault]].contains(delegateHash) - ? true - : checkDelegateForContract(delegate, vault, contract_); - } -} diff --git a/assets/eip-5639/IDelegationRegistry.sol b/assets/eip-5639/IDelegationRegistry.sol deleted file mode 100644 index bbdcb41..0000000 --- a/assets/eip-5639/IDelegationRegistry.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/** - * @title An immutable registry contract to be deployed as a standalone primitive - * @dev New project launches can read previous cold wallet -> hot wallet delegations - * from here and integrate those permissions into their flow - */ -interface IDelegationRegistry { - /// @notice Delegation type - enum DelegationType { - NONE, - ALL, - CONTRACT, - TOKEN - } - - /// @notice Info about a single delegation, used for onchain enumeration - struct DelegationInfo { - DelegationType type_; - address vault; - address delegate; - address contract_; - uint256 tokenId; - } - - /// @notice Info about a single contract-level delegation - struct ContractDelegation { - address contract_; - address delegate; - } - - /// @notice Info about a single token-level delegation - struct TokenDelegation { - address contract_; - uint256 tokenId; - address delegate; - } - - /// @notice Emitted when a user delegates their entire wallet - event DelegateForAll(address vault, address delegate, bool value); - - /// @notice Emitted when a user delegates a specific contract - event DelegateForContract(address vault, address delegate, address contract_, bool value); - - /// @notice Emitted when a user delegates a specific token - event DelegateForToken(address vault, address delegate, address contract_, uint256 tokenId, bool value); - - /// @notice Emitted when a user revokes all delegations - event RevokeAllDelegates(address vault); - - /// @notice Emitted when a user revoes all delegations for a given delegate - event RevokeDelegate(address vault, address delegate); - - /** - * ----------- WRITE ----------- - */ - - /** - * @notice Allow the delegate to act on your behalf for all contracts - * @param delegate The hotwallet to act on your behalf - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForAll(address delegate, bool value) external; - - /** - * @notice Allow the delegate to act on your behalf for a specific contract - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForContract(address delegate, address contract_, bool value) external; - - /** - * @notice Allow the delegate to act on your behalf for a specific token - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param tokenId The token id for the token you're delegating - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external; - - /** - * @notice Revoke all delegates - */ - function revokeAllDelegates() external; - - /** - * @notice Revoke a specific delegate for all their permissions - * @param delegate The hotwallet to revoke - */ - function revokeDelegate(address delegate) external; - - /** - * @notice Remove yourself as a delegate for a specific vault - * @param vault The vault which delegated to the msg.sender, and should be removed - */ - function revokeSelf(address vault) external; - - /** - * ----------- READ ----------- - */ - - /** - * @notice Returns all active delegations a given delegate is able to claim on behalf of - * @param delegate The delegate that you would like to retrieve delegations for - * @return info Array of DelegationInfo structs - */ - function getDelegationsByDelegate(address delegate) external view returns (DelegationInfo[] memory); - - /** - * @notice Returns an array of wallet-level delegates for a given vault - * @param vault The cold wallet who issued the delegation - * @return addresses Array of wallet-level delegates for a given vault - */ - function getDelegatesForAll(address vault) external view returns (address[] memory); - - /** - * @notice Returns an array of contract-level delegates for a given vault and contract - * @param vault The cold wallet who issued the delegation - * @param contract_ The address for the contract you're delegating - * @return addresses Array of contract-level delegates for a given vault and contract - */ - function getDelegatesForContract(address vault, address contract_) external view returns (address[] memory); - - /** - * @notice Returns an array of contract-level delegates for a given vault's token - * @param vault The cold wallet who issued the delegation - * @param contract_ The address for the contract holding the token - * @param tokenId The token id for the token you're delegating - * @return addresses Array of contract-level delegates for a given vault's token - */ - function getDelegatesForToken(address vault, address contract_, uint256 tokenId) - external - view - returns (address[] memory); - - /** - * @notice Returns all contract-level delegations for a given vault - * @param vault The cold wallet who issued the delegations - * @return delegations Array of ContractDelegation structs - */ - function getContractLevelDelegations(address vault) - external - view - returns (ContractDelegation[] memory delegations); - - /** - * @notice Returns all token-level delegations for a given vault - * @param vault The cold wallet who issued the delegations - * @return delegations Array of TokenDelegation structs - */ - function getTokenLevelDelegations(address vault) external view returns (TokenDelegation[] memory delegations); - - /** - * @notice Returns true if the address is delegated to act on the entire vault - * @param delegate The hotwallet to act on your behalf - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForAll(address delegate, address vault) external view returns (bool); - - /** - * @notice Returns true if the address is delegated to act on your behalf for a token contract or an entire vault - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForContract(address delegate, address vault, address contract_) - external - view - returns (bool); - - /** - * @notice Returns true if the address is delegated to act on your behalf for a specific token, the token's contract or an entire vault - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param tokenId The token id for the token you're delegating - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId) - external - view - returns (bool); -} diff --git a/assets/eip-5646/support-per-abi.png b/assets/eip-5646/support-per-abi.png deleted file mode 100644 index d9efc72..0000000 Binary files a/assets/eip-5646/support-per-abi.png and /dev/null differ diff --git a/assets/eip-5646/support-per-eip.png b/assets/eip-5646/support-per-eip.png deleted file mode 100644 index b985faa..0000000 Binary files a/assets/eip-5646/support-per-eip.png and /dev/null differ diff --git a/assets/eip-5700/erc1155/ERC1155.sol b/assets/eip-5700/erc1155/ERC1155.sol deleted file mode 100644 index 652ebb7..0000000 --- a/assets/eip-5700/erc1155/ERC1155.sol +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; - -import {IERC1155Errors} from "../interfaces/IERC1155Errors.sol"; - -/// @title Dopamine Minimal ERC-1155 Contract -/// @notice This is a minimal ERC-1155 implementation that -contract ERC1155 is IERC1155, IERC1155Errors { - - /// @notice Checks for an owner if an address is an authorized operator. - mapping(address => mapping(address => bool)) public isApprovedForAll; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC1155_INTERFACE_ID = 0xd9b67a26; - - /// @notice Gets an address' number of tokens owned of a specific type. - mapping(address => mapping(uint256 => uint256)) public _balanceOf; - - /// @notice Transfers `amount` tokens of id `id` from address `from` to - /// address `to`, while ensuring `to` is capable of receiving the token. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the token to be transferred. - /// @param to The new owner address of the token being transferred. - /// @param id The id of the token being transferred. - /// @param amount The number of tokens being transferred. - /// @param data Additional transfer data to pass to the receiving contract. - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public virtual { - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - _balanceOf[from][id] -= amount; - _balanceOf[to][id] += amount; - - emit TransferSingle(msg.sender, from, to, id, amount); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155Received( - msg.sender, - address(0), - id, - amount, - data - ) != - IERC1155Receiver.onERC1155Received.selector - ) { - revert SafeTransferUnsupported(); - } else if (to == address(0)) { - revert ReceiverInvalid(); - } - } - - /// @notice Transfers tokens `ids` in corresponding batches `amounts` from - /// address `from` to address `to`, while ensuring `to` can receive tokens. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the token to be transferred. - /// @param to The new owner address of the token being transferred. - /// @param ids A list of the token ids being transferred. - /// @param amounts A list of the amounts of each token id being transferred. - /// @param data Additional transfer data to pass to the receiving contract. - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public virtual { - if (ids.length != amounts.length) { - revert ArityMismatch(); - } - - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - uint256 id; - uint256 amount; - - for (uint256 i = 0; i < ids.length; ) { - id = ids[i]; - amount = amounts[i]; - _balanceOf[from][id] -= amount; - _balanceOf[to][id] += amount; - unchecked { - ++i; - } - } - - emit TransferBatch(msg.sender, from, to, ids, amounts); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155BatchReceived( - msg.sender, - from, - ids, - amounts, - data - ) != - IERC1155Receiver.onERC1155BatchReceived.selector - ) { - revert SafeTransferUnsupported(); - } else if (to == address(0)) { - revert ReceiverInvalid(); - } - } - - /// @notice Retrieves balance of address `owner` for token of id `id`. - /// @param owner The token owner's address. - /// @param id The id of the token being queried. - /// @return The number of tokens address `owner` owns of type `id`. - function balanceOf(address owner, uint256 id) public view virtual returns (uint256) { - return _balanceOf[owner][id]; - } - - /// @notice Retrieves balances of multiple owner / token type pairs. - /// @param owners List of token owner addresses. - /// @param ids List of token type identifiers. - /// @return balances List of balances corresponding to the owner / id pairs. - function balanceOfBatch(address[] memory owners, uint256[] memory ids) - public - view - virtual - returns (uint256[] memory balances) - { - if (owners.length != ids.length) { - revert ArityMismatch(); - } - - balances = new uint256[](owners.length); - - unchecked { - for (uint256 i = 0; i < owners.length; ++i) { - balances[i] = _balanceOf[owners[i]][ids[i]]; - } - } - } - - /// @notice Sets the operator for the sender address. - function setApprovalForAll(address operator, bool approved) public virtual { - isApprovedForAll[msg.sender][operator] = approved; - } - - /// @notice Checks if interface of identifier `id` is supported. - /// @param id The ERC-165 interface identifier. - /// @return True if interface id `id` is supported, False otherwise. - function supportsInterface(bytes4 id) public pure virtual returns (bool) { - return - id == _ERC165_INTERFACE_ID || - id == _ERC1155_INTERFACE_ID; - } - - /// @notice Mints token of id `id` to address `to`. - /// @param to Address receiving the minted NFT. - /// @param id The id of the token type being minted. - function _mint(address to, uint256 id) internal virtual { - unchecked { - ++_balanceOf[to][id]; - } - - emit TransferSingle(msg.sender, address(0), to, id, 1); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155Received( - msg.sender, - address(0), - id, - 1, - "" - ) != - IERC1155Receiver.onERC1155Received.selector - ) { - revert SafeTransferUnsupported(); - } else if (to == address(0)) { - revert ReceiverInvalid(); - } - } - -} - diff --git a/assets/eip-5700/erc1155/ERC1155Bindable.sol b/assets/eip-5700/erc1155/ERC1155Bindable.sol deleted file mode 100644 index d7615a2..0000000 --- a/assets/eip-5700/erc1155/ERC1155Bindable.sol +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; - -import {ERC1155} from "./ERC1155.sol"; -import {IERC1155Bindable} from "../interfaces/IERC1155Bindable.sol"; -import {IERC1155Binder} from "../interfaces/IERC1155Binder.sol"; - -/// @title ERC-1155 Bindable Reference Implementation. -/// @dev Only supports the "delegated" binding mode. -contract ERC1155Bindable is ERC1155, IERC1155Bindable { - - /// @notice Tracks the bound balance of an asset for a specific token type. - mapping(address => mapping(uint256 => mapping(uint256 => uint256))) public boundBalanceOf; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC1155_BINDER_INTERFACE_ID = 0x2ac2d2bc; - bytes4 private constant _ERC1155_BINDABLE_INTERFACE_ID = 0xd92c3ff0; - - /// @inheritdoc IERC1155Bindable - function boundBalanceOfBatch( - address bindAddress, - uint256[] calldata bindIds, - uint256[] calldata tokenIds - ) public view returns (uint256[] memory balances) { - if (bindIds.length != tokenIds.length) { - revert ArityMismatch(); - } - - balances = new uint256[](bindIds.length); - - unchecked { - for (uint256 i = 0; i < bindIds.length; ++i) { - balances[i] = boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]]; - } - } - } - - /// @inheritdoc IERC1155Bindable - function bind( - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address bindAddress, - bytes calldata data - ) public { - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - IERC1155Binder binder = IERC1155Binder(bindAddress); - if (to != bindAddress) { - revert BinderInvalid(); - } - - boundBalanceOf[bindAddress][bindId][tokenId] += amount; - _balanceOf[from][tokenId] -= amount; - _balanceOf[to][tokenId] += amount; - - emit Bind(msg.sender, from, bindAddress, tokenId, amount, bindId, bindAddress); - emit TransferSingle(msg.sender, from, bindAddress, tokenId, amount); - - if ( - binder.onERC1155Bind(msg.sender, from, to, tokenId, amount, bindId, data) - != - IERC1155Binder.onERC1155Bind.selector - ) { - revert BindInvalid(); - } - - } - - /// @inheritdoc IERC1155Bindable - function batchBind( - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - address bindAddress, - bytes calldata data - ) public { - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - IERC1155Binder binder = IERC1155Binder(bindAddress); - if (to != bindAddress) { - revert BinderInvalid(); - } - - if (tokenIds.length != amounts.length || tokenIds.length != bindIds.length) { - revert ArityMismatch(); - } - - for (uint256 i = 0; i < tokenIds.length; i++) { - - boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]] += amounts[i]; - _balanceOf[from][tokenIds[i]] -= amounts[i]; - _balanceOf[to][tokenIds[i]] += amounts[i]; - } - - emit BindBatch(msg.sender, from, bindAddress, tokenIds, amounts, bindIds, bindAddress); - emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts); - - if ( - binder.onERC1155BatchBind(msg.sender, from, to, tokenIds, amounts, bindIds, data) - != - IERC1155Binder.onERC1155Bind.selector - ) { - revert BindInvalid(); - } - - } - - /// @inheritdoc IERC1155Bindable - function unbind( - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address bindAddress, - bytes calldata data - ) public { - IERC1155Binder binder = IERC1155Binder(bindAddress); - if ( - binder.ownerOf(bindId) != from - ) { - revert BinderInvalid(); - } - - if ( - msg.sender != from && - !binder.isApprovedForAll(from, msg.sender) - ) { - revert SenderUnauthorized(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - _balanceOf[to][tokenId] += amount; - _balanceOf[from][tokenId] -= amount; - boundBalanceOf[bindAddress][bindId][tokenId] -= amount; - - emit Bind(msg.sender, bindAddress, to, tokenId, amount, bindId, bindAddress); - emit TransferSingle(msg.sender, bindAddress, to, tokenId, amount); - - if ( - binder.onERC1155Unbind(msg.sender, from, to, tokenId, amount, bindId, data) - != - IERC1155Binder.onERC1155Unbind.selector - ) { - revert BindInvalid(); - } - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155Received(msg.sender, from, amount, tokenId, "") - != - IERC1155Receiver.onERC1155Received.selector - ) { - revert SafeTransferUnsupported(); - } - - } - - /// @inheritdoc IERC1155Bindable - function batchUnbind( - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - address bindAddress, - bytes calldata data - ) public { - IERC1155Binder binder = IERC1155Binder(bindAddress); - - if ( - msg.sender != from && - !binder.isApprovedForAll(from, msg.sender) - ) { - revert SenderUnauthorized(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - if (tokenIds.length != amounts.length || tokenIds.length != bindIds.length) { - revert ArityMismatch(); - } - - for (uint256 i = 0; i < tokenIds.length; i++) { - - if (binder.ownerOf(bindIds[i]) != from) { - revert BinderInvalid(); - } - - _balanceOf[to][tokenIds[i]] += amounts[i]; - _balanceOf[from][tokenIds[i]] -= amounts[i]; - boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]] -= amounts[i]; - } - - emit UnbindBatch(msg.sender, from, bindAddress, tokenIds, amounts, bindIds, bindAddress); - emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts); - - if ( - binder.onERC1155BatchUnbind(msg.sender, from, to, tokenIds, amounts, bindIds, data) - != - IERC1155Binder.onERC1155BatchUnbind.selector - ) { - revert BindInvalid(); - } - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155BatchReceived(msg.sender, from, tokenIds, amounts, "") - != - IERC1155Receiver.onERC1155BatchReceived.selector - ) { - revert SafeTransferUnsupported(); - } - } - - - function supportsInterface(bytes4 id) public pure override(ERC1155, IERC165) returns (bool) { - return super.supportsInterface(id) || id == _ERC1155_BINDABLE_INTERFACE_ID; - } - -} diff --git a/assets/eip-5700/erc1155/ERC1155Binder.sol b/assets/eip-5700/erc1155/ERC1155Binder.sol deleted file mode 100644 index 2ac1dcb..0000000 --- a/assets/eip-5700/erc1155/ERC1155Binder.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; - -import {IERC1155Bindable} from "../interfaces/IERC1155Bindable.sol"; -import {IERC1155Binder} from "../interfaces/IERC1155Binder.sol"; - -/// @title ERC-1155 Binder Reference Implementation -contract ERC1155Binder is IERC1155Binder { - - struct Bindable { - address tokenAddress; - uint256 tokenId; - } - - /// @notice Checks for an owner if an address is an authorized operator. - mapping(address => mapping(address => bool)) public _isApprovedForAll; - - /// @notice Tracks ownership of bound assets. - mapping(uint256 => address) _ownerOf; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC1155_BINDER_INTERFACE_ID = 0x2ac2d2bc; - bytes4 private constant _ERC1155_BINDABLE_INTERFACE_ID = 0xd92c3ff0; - - /// @inheritdoc IERC1155Binder - function isApprovedForAll(address owner, address operator) external view override returns (bool) { - return _isApprovedForAll[owner][operator]; - } - - /// @inheritdoc IERC1155Binder - function ownerOf(uint256 id) public view returns (address) { - return _ownerOf[id]; - } - - /// @inheritdoc IERC1155Binder - function onERC1155Bind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - bytes calldata data - ) public returns (bytes4) { - return IERC1155Binder.onERC1155Bind.selector; - } - - /// @inheritdoc IERC1155Binder - function onERC1155BatchBind( - address operator, - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - bytes calldata data - ) public returns (bytes4) { - return IERC1155Binder.onERC1155BatchBind.selector; - } - - /// @inheritdoc IERC1155Binder - function onERC1155Unbind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - bytes calldata data - ) public returns (bytes4) { - return IERC1155Binder.onERC1155Unbind.selector; - } - - /// @inheritdoc IERC1155Binder - function onERC1155BatchUnbind( - address operator, - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - bytes calldata data - ) public returns (bytes4) { - return IERC1155Binder.onERC1155BatchUnbind.selector; - } - - function supportsInterface(bytes4 id) external pure returns (bool) { - return id == _ERC165_INTERFACE_ID || id == _ERC1155_BINDER_INTERFACE_ID; - } - - /// @notice Mints a new asset identified by `id` to address `to`. - function _mint(address to, uint256 id) internal { - if (to == address(0)) { - revert ReceiverInvalid(); - } - - if (_ownerOf[id] != address(0)) { - revert AssetAlreadyMinted(); - } - - _ownerOf[id] = to; - } - -} diff --git a/assets/eip-5700/erc721/ERC721.sol b/assets/eip-5700/erc721/ERC721.sol deleted file mode 100644 index c3f50d9..0000000 --- a/assets/eip-5700/erc721/ERC721.sol +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -import {IERC721Errors} from "../interfaces/IERC721Errors.sol"; - -/// @title Reference Minimal ERC-721 Contract -contract ERC721 is IERC721, IERC721Errors { - - /// @notice The total number of NFTs in circulation. - uint256 public totalSupply; - - /// @notice Gets the approved address for an NFT. - /// @dev This implementation does not throw for zero-address queries. - mapping(uint256 => address) public getApproved; - - /// @notice Gets the number of NFTs owned by an address. - mapping(address => uint256) internal _balanceOf; - - /// @dev Tracks the assigned owner of an address. - mapping(uint256 => address) internal _ownerOf; - - /// @dev Checks for an owner if an address is an authorized operator. - mapping(address => mapping(address => bool)) internal _operatorApprovals; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC721_INTERFACE_ID = 0x80ac58cd; - - /// @notice Gets the assigned owner for token `id`. - /// @param id The id of the NFT being queried. - /// @return The address of the owner of the NFT of id `id`. - function ownerOf(uint256 id) external view virtual returns (address) { - return _ownerOf[id]; - } - - /// @notice Gets number of NFTs owned by address `owner`. - /// @param owner The address whose balance is being queried. - /// @return The number of NFTs owned by address `owner`. - function balanceOf(address owner) external view virtual returns (uint256) { - return _balanceOf[owner]; - } - - /// @notice Sets approved address of NFT of id `id` to address `approved`. - /// @param approved The new approved address for the NFT. - /// @param id The id of the NFT to approve. - function approve(address approved, uint256 id) external virtual { - address owner = _ownerOf[id]; - - if (msg.sender != owner && !_operatorApprovals[owner][msg.sender]) { - revert SenderUnauthorized(); - } - - getApproved[id] = approved; - emit Approval(owner, approved, id); - } - - /// @notice Checks if `operator` is an authorized operator for `owner`. - /// @param owner The address of the owner. - /// @param operator The address of the owner's operator. - /// @return True if `operator` is approved operator of `owner`, else False. - function isApprovedForAll(address owner, address operator) - external - view - virtual returns (bool) - { - return _operatorApprovals[owner][operator]; - } - - /// @notice Sets the operator for `msg.sender` to `operator`. - /// @param operator The operator address that will manage the sender's NFTs. - /// @param approved Whether operator is allowed to operate sender's NFTs. - function setApprovalForAll(address operator, bool approved) external virtual { - _operatorApprovals[msg.sender][operator] = approved; - emit ApprovalForAll(msg.sender, operator, approved); - } - - /// @notice Checks if interface of identifier `id` is supported. - /// @param id The ERC-165 interface identifier. - /// @return True if interface id `id` is supported, false otherwise. - function supportsInterface(bytes4 id) public pure virtual returns (bool) { - return - id == _ERC165_INTERFACE_ID || - id == _ERC721_INTERFACE_ID; - } - - /// @notice Transfers NFT of id `id` from address `from` to address `to`, - /// with safety checks ensuring `to` is capable of receiving the NFT. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the NFT to be transferred. - /// @param to The new owner address of the NFT being transferred. - /// @param id The id of the NFT being transferred. - /// @param data Additional transfer data to pass to the receiving contract. - function safeTransferFrom( - address from, - address to, - uint256 id, - bytes memory data - ) public virtual { - transferFrom(from, to, id); - - if ( - to.code.length != 0 && - IERC721Receiver(to).onERC721Received(msg.sender, from, id, data) - != - IERC721Receiver.onERC721Received.selector - ) { - revert SafeTransferUnsupported(); - } - } - - /// @notice Transfers NFT of id `id` from address `from` to address `to`, - /// with safety checks ensuring `to` is capable of receiving the NFT. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the NFT to be transferred. - /// @param to The new owner address of the NFT being transferred. - /// @param id The id of the NFT being transferred. - function safeTransferFrom( - address from, - address to, - uint256 id - ) public virtual { - transferFrom(from, to, id); - - if ( - to.code.length != 0 && - IERC721Receiver(to).onERC721Received(msg.sender, from, id, "") - != - IERC721Receiver.onERC721Received.selector - ) { - revert SafeTransferUnsupported(); - } - } - - /// @notice Transfers NFT of id `id` from address `from` to address `to`, - /// without performing any safety checks. - /// @dev Existence of an NFT is inferred by having a non-zero owner address. - /// Transfers clear owner approvals, but `Approval` events are omitted. - /// @param from The existing owner address of the NFT being transferred. - /// @param to The new owner address of the NFT being transferred. - /// @param id The id of the NFT being transferred. - function transferFrom( - address from, - address to, - uint256 id - ) public virtual { - if (from != _ownerOf[id]) { - revert OwnerInvalid(); - } - - if ( - msg.sender != from && - msg.sender != getApproved[id] && - !_operatorApprovals[from][msg.sender] - ) { - revert SenderUnauthorized(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - _beforeTokenTransfer(from, to, id); - - delete getApproved[id]; - - unchecked { - _balanceOf[from]--; - _balanceOf[to]++; - } - - _ownerOf[id] = to; - emit Transfer(from, to, id); - } - - /// @dev Mints NFT of id `id` to address `to`. To save gas, it is assumed - /// that `maxSupply` < `type(uint256).max` (ex. for tabs, cap is very low). - /// @param to Address receiving the minted NFT. - /// @param id Identifier of the NFT being minted. - /// @return The id of the minted NFT. - function _mint(address to, uint256 id) internal virtual returns (uint256) { - if (to == address(0)) { - revert ReceiverInvalid(); - } - if (_ownerOf[id] != address(0)) { - revert TokenAlreadyMinted(); - } - - _beforeTokenTransfer(address(0), to, id); - - unchecked { - totalSupply++; - _balanceOf[to]++; - } - - _ownerOf[id] = to; - emit Transfer(address(0), to, id); - return id; - } - - /// @dev Burns NFT of id `id`, removing it from existence. - /// @param id Identifier of the NFT being burned - function _burn(uint256 id) internal virtual { - address owner = _ownerOf[id]; - - if (owner == address(0)) { - revert TokenNonExistent(); - } - - _beforeTokenTransfer(owner, address(0), id); - - unchecked { - totalSupply--; - _balanceOf[owner]--; - } - - delete _ownerOf[id]; - emit Transfer(owner, address(0), id); - } - - /// @notice Pre-transfer hook for embedding additional transfer behavior. - /// @param from The address of the existing owner of the NFT. - /// @param to The address of the new owner of the NFT. - /// @param id The id of the NFT being transferred. - function _beforeTokenTransfer(address from, address to, uint256 id) - internal - virtual - {} - -} diff --git a/assets/eip-5700/erc721/ERC721Bindable.sol b/assets/eip-5700/erc721/ERC721Bindable.sol deleted file mode 100644 index d772721..0000000 --- a/assets/eip-5700/erc721/ERC721Bindable.sol +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -import {ERC721} from "./ERC721.sol"; -import {IERC721Bindable} from "../interfaces/IERC721Bindable.sol"; -import {IERC721Binder} from "../interfaces/IERC721Binder.sol"; - -/// @title ERC-721 Bindable Reference Implementation. -/// @dev Supports both "legacy" and "delegated" binding modes. -contract ERC721Bindable is ERC721, IERC721Bindable { - - /// @notice Encapsulates a bound asset contract address and identifier. - struct Binder { - address bindAddress; - uint256 bindId; - } - - /// @notice Tracks the bound balance of a specific asset. - mapping(address => mapping(uint256 => uint256)) public boundBalanceOf; - - /// @notice Tracks bound assets of an NFT. - mapping(uint256 => Binder) internal _bound; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC721_BINDER_INTERFACE_ID = 0x2ac2d2bc; - bytes4 private constant _ERC721_BINDABLE_INTERFACE_ID = 0xd92c3ff0; - - /// @inheritdoc IERC721Bindable - function binderOf(uint256 tokenId) public returns (address, uint256) { - Binder memory bound = _bound[tokenId]; - return (bound.bindAddress, bound.bindId); - } - - /// @inheritdoc IERC721Bindable - function bind( - address from, - address to, - uint256 tokenId, - uint256 bindId, - address bindAddress, - bytes calldata data - ) public { - if (_bound[tokenId].bindAddress != address(0)) { - revert BindExistent(); - } - - if (from != _ownerOf[tokenId]) { - revert OwnerInvalid(); - } - - IERC721Binder binder = IERC721Binder(bindAddress); - address assetOwner = binder.ownerOf(bindId); - if (to != assetOwner && to != bindAddress) { - revert BinderInvalid(); - } - - if ( - msg.sender != from && - msg.sender != getApproved[tokenId] && - !_operatorApprovals[from][msg.sender] - ) { - revert SenderUnauthorized(); - } - - delete getApproved[tokenId]; - - unchecked { - _balanceOf[from]--; - _balanceOf[bindAddress]++; - _balanceOf[assetOwner]++; - boundBalanceOf[bindAddress][bindId]++; - } - - _ownerOf[tokenId] = to; - _bound[tokenId] = Binder(bindAddress, bindId); - - emit Bind(msg.sender, from, to, tokenId, bindId, bindAddress); - emit Transfer(from, to, tokenId); - - if ( - binder.onERC721Bind(msg.sender, from, to, tokenId, bindId, "") - != - IERC721Binder.onERC721Bind.selector - ) { - revert BindInvalid(); - } - - } - - /// @inheritdoc IERC721Bindable - function unbind( - address from, - address to, - uint256 tokenId, - uint256 bindId, - address bindAddress, - bytes calldata data - ) public { - Binder memory bound = _bound[tokenId]; - if (bound.bindAddress != address(0)) { - revert BindNonexistent(); - } - - IERC721Binder binder = IERC721Binder(bindAddress); - if ( - bound.bindAddress != bindAddress || - bound.bindId != bindId || - binder.ownerOf(bindId) != from - ) { - revert BinderInvalid(); - } - - if ( - msg.sender != from && - !binder.isApprovedForAll(from, msg.sender) - ) { - revert SenderUnauthorized(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - address delegatedOwner = _ownerOf[tokenId]; - - delete getApproved[tokenId]; - - unchecked { - _balanceOf[to]++; - _balanceOf[from]--; - _balanceOf[bindAddress]--; - boundBalanceOf[bindAddress][bindId]--; - } - - _ownerOf[tokenId] = to; - delete _bound[tokenId]; - - emit Bind(msg.sender, from, to, tokenId, bindId, bindAddress); - emit Transfer(delegatedOwner, to, tokenId); - - if ( - binder.onERC721Unbind(msg.sender, from, to, tokenId, bindId, "") - != - IERC721Binder.onERC721Unbind.selector - ) { - revert BindInvalid(); - } - - if ( - to.code.length != 0 && - IERC721Receiver(to).onERC721Received(msg.sender, delegatedOwner, tokenId, "") - != - IERC721Receiver.onERC721Received.selector - ) { - revert SafeTransferUnsupported(); - } - - } - - /// @inheritdoc IERC721 - function transferFrom( - address from, - address to, - uint256 tokenId - ) public override(IERC721, ERC721) { - - address bindAddress = _bound[tokenId].bindAddress; - uint256 bindId = _bound[tokenId].bindId; - - if (bindAddress == address(0)) { - return super.transferFrom(from, to, tokenId); - } - - if (msg.sender != bindAddress) { - revert BindExistent(); - } - - IERC721Binder binder = IERC721Binder(bindAddress); - - if ( - binder.ownerOf(bindId) != from - ) { - revert BinderInvalid(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - delete getApproved[tokenId]; - - uint256 bindBal = boundBalanceOf[bindAddress][bindId]; - unchecked { - _balanceOf[from] -= bindBal; - _balanceOf[to] += bindBal; - } - - emit Transfer(from, to, tokenId); - } - - function supportsInterface(bytes4 id) public pure override(ERC721, IERC165) returns (bool) { - return super.supportsInterface(id) || id == _ERC721_BINDABLE_INTERFACE_ID; - } - -} diff --git a/assets/eip-5700/erc721/ERC721Binder.sol b/assets/eip-5700/erc721/ERC721Binder.sol deleted file mode 100644 index e8f939a..0000000 --- a/assets/eip-5700/erc721/ERC721Binder.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -import {ERC721} from "./ERC721.sol"; -import {IERC721Bindable} from "../interfaces/IERC721Bindable.sol"; -import {IERC721Binder} from "../interfaces/IERC721Binder.sol"; - -/// @title ERC-721 Binder Reference Implementation -contract ERC721Binder is IERC721Binder { - - struct Bindable { - address tokenAddress; - uint256 tokenId; - } - - /// @notice Checks for an owner if an address is an authorized operator. - mapping(address => mapping(address => bool)) public _isApprovedForAll; - - /// @notice Tracks ownership of bound assets. - mapping(uint256 => address) _ownerOf; - - /// @notice Maps an asset to a list of all bound bindables. - mapping(uint256 => Bindable[]) _boundTokens; - - /// @notice Maps a token address and identifier to the bound tokens index. - mapping(address => mapping(uint256 => uint256)) _boundIndexes; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC721_BINDER_INTERFACE_ID = 0x2ac2d2bc; - bytes4 private constant _ERC721_BINDABLE_INTERFACE_ID = 0xd92c3ff0; - - /// @inheritdoc IERC721Binder - function isApprovedForAll(address owner, address operator) external view override returns (bool) { - return _isApprovedForAll[owner][operator]; - } - - /// @inheritdoc IERC721Binder - function ownerOf(uint256 id) public view returns (address) { - return _ownerOf[id]; - } - - /// @inheritdoc IERC721Binder - function onERC721Bind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 bindId, - bytes calldata data - ) public returns (bytes4) { - if (_ownerOf[bindId] != to) { - revert OwnerInvalid(); - } - - if (_boundIndexes[msg.sender][tokenId] != 0) { - revert BindExistent(); - } - - if (!IERC721Bindable(msg.sender).supportsInterface(_ERC721_BINDABLE_INTERFACE_ID)) { - revert BindInvalid(); - } - - _boundIndexes[msg.sender][tokenId] = _boundTokens[bindId].length; - _boundTokens[bindId].push(Bindable(msg.sender, tokenId)); - - return IERC721Binder.onERC721Bind.selector; - } - - /// @inheritdoc IERC721Binder - function onERC721Unbind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 bindId, - bytes calldata data - ) public returns (bytes4) { - if (_ownerOf[bindId] != from) { - revert OwnerInvalid(); - } - - if (_boundIndexes[msg.sender][tokenId] == 0) { - revert BindNonexistent(); - } - - uint256 boundLastIndex = _boundTokens[bindId].length - 1; - uint256 boundIndex = _boundIndexes[msg.sender][tokenId]; - - if (boundIndex != boundLastIndex) { - Bindable memory bindable = _boundTokens[bindId][boundLastIndex]; - _boundTokens[bindId][boundIndex] = bindable; - _boundIndexes[bindable.tokenAddress][bindable.tokenId] = boundIndex; - } - - delete _boundIndexes[msg.sender][tokenId]; - delete _boundTokens[bindId][boundLastIndex]; - - return IERC721Binder.onERC721Unbind.selector; - } - - /// @notice Transfers an asset from address `from` to address `to`. - function transfer( - address from, - address to, - uint256 bindId - ) public { - if (msg.sender != from && !_isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - if (from != _ownerOf[bindId]) { - revert OwnerInvalid(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - _ownerOf[bindId] = to; - - Bindable[] memory bindables = _boundTokens[bindId]; - for (uint256 i = 0; i < bindables.length; ++i) { - IERC721Bindable(bindables[i].tokenAddress).transferFrom(from, to, bindables[i].tokenId); - } - - } - - function supportsInterface(bytes4 id) external pure returns (bool) { - return id == _ERC165_INTERFACE_ID || id == _ERC721_BINDER_INTERFACE_ID; - } - -} diff --git a/assets/eip-5700/interfaces/IERC1155Bindable.sol b/assets/eip-5700/interfaces/IERC1155Bindable.sol deleted file mode 100644 index c4d491a..0000000 --- a/assets/eip-5700/interfaces/IERC1155Bindable.sol +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; - -import {IERC1155BindableErrors} from "./IERC1155BindableErrors.sol"; - -/// @title ERC-1155 Bindable Token Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-5656 -/// Note: the ERC-165 identifier for this interface is 0xd0d555c6. -interface IERC1155Bindable is IERC1155, IERC1155BindableErrors { - - /// @notice The `Bind` event MUST emit when token ownership is delegated - /// through an asset and when minting tokens bound to an existing asset. - /// @dev When minting bound tokens, `from` MUST be set to the zero address. - /// @param operator The address calling the bind (SHOULD be `msg.sender`). - /// @param from The address which owns the unbound token(s). - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the token type being bound. - /// @param amount The number of tokens of type `tokenId` being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param bindAddress The contract address handling asset ownership. - event Bind( - address indexed operator, - address indexed from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address indexed bindAddress - ); - - /// @notice The `BindBatch` event MUST emit when token ownership of - /// different token types are delegated through different assets at once - /// and when minting multiple token types bound to existing assets at once. - /// @dev When minting bound tokens, `from` MUST be set to the zero address. - /// @param operator The address calling the bind (SHOULD be `msg.sender`). - /// @param from The address which owns the unbound token(s). - /// @param to The address which owns the asset being bound to. - /// @param tokenIds The identifiers of the token types being bound. - /// @param amounts The number of tokens for each token type being bound. - /// @param bindIds The identifiers of the assets being bound to. - /// @param bindAddress The contract address handling asset ownership. - event BindBatch( - address indexed operator, - address indexed from, - address to, - uint256[] tokenIds, - uint256[] amounts, - uint256[] bindIds, - address indexed bindAddress - ); - - /// @notice The `Unbind` event MUST emit when asset-delegated token - /// ownership is revoked and when burning tokens bound to existing assets. - /// @dev When burning bound tokens, `to` MUST be set to the zero address. - /// @param operator The address calling the unbind (SHOULD be `msg.sender`). - /// @param from The address which owns the asset the token(s) are bound to. - /// @param to The address which will own the token(s) once unbound. - /// @param tokenId The identifier of the token type being unbound. - /// @param amount The number of tokens of type `tokenId` being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - event Unbind( - address indexed operator, - address indexed from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address indexed bindAddress - ); - - /// @notice The `UnbindBatch` event MUST emit when asset-delegated token - /// ownership is revoked for multiple token types at once and when burning - /// multiple token types bound to existing assets at once. - /// @dev When burning bound tokens, `to` MUST be set to the zero address. - /// @param operator The address calling the unbind (SHOULD be `msg.sender`). - /// @param from The address which owns the asset the token(s) are bound to. - /// @param to The address which will own the token(s) once unbound. - /// @param tokenIds The identifiers of the token types being unbound. - /// @param amounts The number of tokens for each token type being unbound. - /// @param bindIds The identifier of the assets being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - event UnbindBatch( - address indexed operator, - address indexed from, - address to, - uint256[] tokenIds, - uint256[] amounts, - uint256[] bindIds, - address indexed bindAddress - ); - - /// @notice Delegates ownership of `amount` tokens of type `tokenId` from - /// address `from` through asset `bindId` owned by address `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. The function also MUST throw if `from` owns fewer than - /// `amount` unbound tokens, or if `to` is not the asset owner. After - /// delegation of ownership, the function MUST check if `bindAddress` is a - /// valid contract (code size > 0), and if so, call `onERC1155Bind` on the - /// contract, throwing if the wrong identifier is returned (see "Binding - /// Rules") or if the contract is invalid. On bind completion, the function - /// MUST emit both `Bind` and IERC-1155 `TransferSingle` events to reflect - /// delegated ownership change. - /// @param from The address which owns the unbound token(s). - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the token type being bound. - /// @param amount The number of tokens of type `tokenId` being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param bindAddress The contract address handling asset ownership. - /// @param data Additional data sent with the `onERC1155Bind` hook. - function bind( - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Delegates ownership of `amounts` tokens of types `tokenIds` from - /// address `from` through assets `bindIds` owned by address `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. The function also MUST throw if length of `amounts` is not - /// the same as `tokenIds` or `bindIds`, if any unbound balances of - /// `tokenIds` for `from` is less than that of `amounts`, or if `to` is not - /// the asset owner. After delegating ownership, the function MUST check if - /// `bindAddress` is a valid contract (code size > 0), and if so, call - /// `onERC1155BatchBind` on the contract, throwing if the wrong identifier - /// is returned (see "Binding Rules") or if the contract is invalid. On - /// bind completion, the function MUST emit both `BindBatch` and IERC-1155 - /// `TransferBatch` events to reflect delegated ownership changes. - /// @param from The address which owns the unbound tokens. - /// @param to The address which owns the assets being bound to. - /// @param tokenIds The identifiers of the token types being bound. - /// @param amounts The number of tokens for each token type being bound. - /// @param bindIds The identifiers of the assets being bound to. - /// @param bindAddress The contract address handling asset ownership. - /// @param data Additional data sent with the `onERC1155BatchBind` hook. - function batchBind( - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Revokes delegated ownership of `amount` tokens of type `tokenId` - /// owned by `from` bound to `bindId`, binding direct ownership to `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// or owner of the delegated asset `tokenId` is bound to. It also MUST - /// throw if `from` owns fewer than `amount` bound tokens, or if `to` is - /// the zero address. Once delegated ownership is revoked, the function - /// MUST check if `bindAddress` is a valid contract (code size > 0), and if - /// so, call `onERC1155Unbind` on the contract, throwing if the wrong - /// identifier is returned (see "Binding Rules") or if the contract is - /// invalid. The function also MUST check if `to` is a contract, and if so, - /// call on it `onERC1155Received`, throwing if the wrong identifier is - /// returned. On unbind completion, the function MUST emit both `Unbind` - /// and IERC-1155 `TransferSingle` events to reflect delegated ownership change. - /// @param from The address which owns the asset the token(s) are bound to. - /// @param to The address which will own the tokens once unbound. - /// @param tokenId The identifier of the token type being unbound. - /// @param amount The number of tokens of type `tokenId` being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param data Additional data sent with the `onERC1155Unbind` hook. - function unbind( - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Revokes delegated ownership of `amounts` tokens of `tokenIds` - /// bound to assets `bindIds`, binding direct ownership to `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// or owner of all delegated assets `tokenIds` are bound to. It also MUST - /// throw if the length of `amounts` is not the same as `tokenIds` or - /// `bindIds`, if any bound balances of `tokenId` for `from` is less than - /// that of `amounts`, or if `to` is the zero address. Once delegated - /// ownership is revoked, the function MUST check if `bindAddress` is a - /// valid contract (code size > 0), and if so, call onERC1155BatchUnbind` - /// on it, throwing if a wrong identifier is returned (see "Binding Rules") - /// or if the contract is invalid. The function also MUST check if `to` is - /// a valid contract, and if so, call `onERC1155BatchReceived`, throwing if - /// the wrong identifier is returned. On unbind completion, the function - /// MUST emit the `BatchUnbind` and IERC-1155 `TransferBatch` events to - /// reflect delegated ownership changes. - /// @param from The address which owns the asset the tokens are bound to. - /// @param to The address which will own the tokens once unbound. - /// @param tokenIds The identifiers of the token types being unbound. - /// @param amounts The number of tokens for each token type being unbound. - /// @param bindIds The identifier of the assets being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param data Additional data sent with the `onERC1155BatchUnbind` hook. - function batchUnbind( - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Gets the balance of bound tokens of type `tokenId` bound to the - /// asset `bindId` at address `bindAddress`. - /// @param bindId The identifier of the bound asset. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param tokenId The identifier of the bound token type being counted. - /// @return The total number of NFTs bound to the asset. - function boundBalanceOf( - address bindAddress, - uint256 bindId, - uint256 tokenId - ) external returns (uint256); - - /// @notice Gets the balance of bound tokens for multiple token types given - /// by `tokenIds` bound to assets `bindIds` at address `bindAddress`. - /// @notice Retrieves bound balances of multiple asset / token type pairs. - /// @param bindIds List of bound asset identifiers. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param tokenIds The identifiers of the token type being counted. - /// @return balances The bound balances for each asset / token type pair. - function boundBalanceOfBatch( - address bindAddress, - uint256[] calldata bindIds, - uint256[] calldata tokenIds - ) external returns (uint256[] memory balances); - -} diff --git a/assets/eip-5700/interfaces/IERC1155BindableErrors.sol b/assets/eip-5700/interfaces/IERC1155BindableErrors.sol deleted file mode 100644 index 4b6aefa..0000000 --- a/assets/eip-5700/interfaces/IERC1155BindableErrors.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-1155 Bindable Errors Interface -interface IERC1155BindableErrors { - - /// @notice Bind is not valid. - error BindInvalid(); - - /// @notice Bound asset or bound asset owner is not valid. - error BinderInvalid(); - -} diff --git a/assets/eip-5700/interfaces/IERC1155Binder.sol b/assets/eip-5700/interfaces/IERC1155Binder.sol deleted file mode 100644 index 4466ae7..0000000 --- a/assets/eip-5700/interfaces/IERC1155Binder.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -import {IERC1155BinderErrors} from "./IERC1155BinderErrors.sol"; - -/// @dev Note: the ERC-165 identifier for this interface is 0x6fc97e78. -interface IERC1155Binder is IERC165, IERC1155BinderErrors { - - /// @notice Handles binding of an IERC1155Bindable-compliant token type. - /// @dev An IERC1155Bindable-compliant smart contract MUST call this - /// function at the end of a `bind` after delegating ownership to the asset - /// owner. The function MUST revert if `to` is not the asset owner of - /// `bindId`, or if `bindId` is not a valid asset. The function MUST revert - /// if it rejects the bind. If accepting the bind, the function MUST return - /// `bytes4(keccak256("onERC1155Bind(address,address,address,uint256,uint256,uint256,bytes)"))` - /// Caller MUST revert the transaction if the above value is not returned. - /// Note: The contract address of the binding token is `msg.sender`. - /// @param operator The address responsible for binding. - /// @param from The address which owns the unbound tokens. - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the token type being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param data Additional data sent along with no specified format. - /// @return `bytes4(keccak256("onERC1155Bind(address,address,address,uint256,uint256,uint256,bytes)"))` - function onERC1155Bind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - bytes calldata data - ) external returns (bytes4); - - /// @notice Handles binding of multiple IERC1155Bindable-compliant tokens - /// `tokenIds` to multiple assets `bindIds`. - /// @dev An IERC1155Bindable-compliant smart contract MUST call this - /// function at the end of a `batchBind` after delegating ownership of - /// multiple token types to the asset owner. The function MUST revert if - /// `to` is not the asset owner of `bindId`, or if `bindId` is not a valid - /// asset. The function MUST revert if it rejects the binds. If accepting - /// the binds, the function MUST return `bytes4(keccak256("onERC1155BatchBind(address,address,address,uint256[],uint256[],uint256[],bytes)"))` - /// Caller MUST revert the transaction if the above value is not returned. - /// Note: The contract address of the binding token is `msg.sender`. - /// @param operator The address responsible for performing the binds. - /// @param from The address which owns the unbound tokens. - /// @param to The address which owns the assets being bound to. - /// @param tokenIds The list of token types being bound. - /// @param amounts The number of tokens for each token type being bound. - /// @param bindIds The identifiers of the assets being bound to. - /// @param data Additional data sent along with no specified format. - /// @return `bytes4(keccak256("onERC1155Bind(address,address,address,uint256[],uint256[],uint256[],bytes)"))` - function onERC1155BatchBind( - address operator, - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - bytes calldata data - ) external returns (bytes4); - - /// @notice Handles unbinding of an IERC1155Bindable-compliant token type. - /// @dev An IERC1155Bindable-compliant contract MUST call this function at - /// the end of an `unbind` after revoking delegated asset ownership. The - /// function MUST revert if `from` is not the asset owner of `bindId`, - /// or if `bindId` is not a valid asset. The function MUST revert if it - /// rejects the unbind. If accepting the unbind, the function MUST return - /// `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256,uint256,uint256,bytes)"))` - /// Caller MUST revert the transaction if the above value is not returned. - /// Note: The contract address of the unbinding token is `msg.sender`. - /// @param operator The address responsible for performing the unbind. - /// @param from The address which owns the asset the token type is bound to. - /// @param to The address which will own the tokens once unbound. - /// @param tokenId The token type being unbound. - /// @param amount The number of tokens of type `tokenId` being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param data Additional data sent along with no specified format. - /// @return `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256,uint256,uint256,bytes)"))` - function onERC1155Unbind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - bytes calldata data - ) external returns (bytes4); - - /// @notice Handles unbinding of multiple IERC1155Bindable-compliant token types. - /// @dev An IERC1155Bindable-compliant contract MUST call this function at - /// the end of an `batchUnbind` after revoking delegated asset ownership. - /// The function MUST revert if `from` is not the asset owner of `bindId`, - /// or if `bindId` is not a valid asset. The function MUST revert if it - /// rejects the unbinds. If accepting the unbinds, the function MUST return - /// `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256[],uint256[],uint256[],bytes)"))` - /// Caller MUST revert the transaction if the above value is not returned. - /// Note: The contract address of the unbinding token is `msg.sender`. - /// @param operator The address responsible for performing the unbinds. - /// @param from The address which owns the assets being unbound from. - /// @param to The address which will own the tokens once unbound. - /// @param tokenIds The list of token types being unbound. - /// @param amounts The number of tokens for each token type being unbound. - /// @param bindIds The identifiers of the assets being unbound from. - /// @param data Additional data sent along with no specified format. - /// @return `bytes4(keccak256("onERC1155Unbind(address,address,address,uint256[],uint256[],uint256[],bytes)"))` - function onERC1155BatchUnbind( - address operator, - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - bytes calldata data - ) external returns (bytes4); - - /// @notice Gets the owner address of the asset represented by id `bindId`. - /// @param bindId The identifier of the asset whose owner is being queried. - /// @return The address of the owner of the asset. - function ownerOf(uint256 bindId) external view returns (address); - - /// @notice Checks if an operator can act on behalf of an asset owner. - /// @param owner The address that owns an asset. - /// @param operator The address that acts on behalf of owner `owner`. - /// @return True if `operator` can act on behalf of `owner`, else False. - function isApprovedForAll(address owner, address operator) external view returns (bool); - -} diff --git a/assets/eip-5700/interfaces/IERC1155BinderErrors.sol b/assets/eip-5700/interfaces/IERC1155BinderErrors.sol deleted file mode 100644 index 9c43833..0000000 --- a/assets/eip-5700/interfaces/IERC1155BinderErrors.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-1155 Binder Errors Interface -interface IERC1155BinderErrors { - - /// @notice Asset has already minted. - error AssetAlreadyMinted(); - - /// @notice Receiving address cannot be the zero address. - error ReceiverInvalid(); - -} diff --git a/assets/eip-5700/interfaces/IERC1155Errors.sol b/assets/eip-5700/interfaces/IERC1155Errors.sol deleted file mode 100644 index d3f0712..0000000 --- a/assets/eip-5700/interfaces/IERC1155Errors.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-1155 Errors Interface -interface IERC1155Errors { - - /// @notice Arity mismatch between two arrays. - error ArityMismatch(); - - /// @notice Originating address does not own the NFT. - error OwnerInvalid(); - - /// @notice Receiving address cannot be the zero address. - error ReceiverInvalid(); - - /// @notice Receiving contract does not implement the ERC-1155 wallet interface. - error SafeTransferUnsupported(); - - /// @notice Sender is not NFT owner, approved address, or owner operator. - error SenderUnauthorized(); - - /// @notice Token has already minted. - error TokenAlreadyMinted(); - - /// @notice NFT does not exist. - error TokenNonExistent(); - -} diff --git a/assets/eip-5700/interfaces/IERC721Bindable.sol b/assets/eip-5700/interfaces/IERC721Bindable.sol deleted file mode 100644 index f8ac283..0000000 --- a/assets/eip-5700/interfaces/IERC721Bindable.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -import {IERC721BindableErrors} from "./IERC721BindableErrors.sol"; - -/// @title ERC-721 Bindable Token Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-5700 -/// Note: the ERC-165 identifier for this interface is 0x82a34a7d. -interface IERC721Bindable is IERC721, IERC721BindableErrors { - - /// @notice The `Bind` event MUST emit when NFT ownership is delegated - /// through an asset and when minting an NFT bound to an existing asset. - /// @dev When minting bound NFTs, `from` MUST be set to the zero address. - /// @param operator The address calling the bind (SHOULD be `msg.sender`). - /// @param from The address which owns the unbound NFT. - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the NFT being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param bindAddress The contract address handling asset ownership. - event Bind( - address indexed operator, - address indexed from, - address to, - uint256 tokenId, - uint256 bindId, - address indexed bindAddress - ); - - /// @notice The `Unbind` event MUST emit when asset-delegated NFT ownership - /// is revoked, as well as when burning an NFT bound to an existing asset. - /// @dev When burning bound NFTs, `to` MUST be set to the zero address. - /// @param operator The address calling the unbind (SHOULD be `msg.sender`). - /// @param from The address which owns the asset the NFT is bound to. - /// @param to The address which will own the NFT once unbound. - /// @param tokenId The identifier of the NFT being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - event Unbind( - address indexed operator, - address indexed from, - address to, - uint256 tokenId, - uint256 bindId, - address indexed bindAddress - ); - - /// @notice Delegates NFT ownership of NFT `tokenId` from address `from` - /// through the asset `bindId` owned by address `to`. - /// @dev The function MUST throw unless `msg.sender` is the current owner, - /// an authorized operator, or the approved address for the NFT. It also - /// MUST throw if NFT `tokenId` is already bound, if `from` is not the NFT - /// owner, or if `to` is not the asset owner. After ownership delegation, - /// the function MUST check if `bindAddress` is a valid contract (code size - /// > 0), and if so, call `onERC721Bind` on the contract, throwing if the - /// wrong identifier is returned (see "Binding Rules") or if the contract - /// is invalid. On bind completion, the function MUST emit both `Bind` and - /// IERC-721 `Transfer` events to reflect delegated ownership change. - /// @param from The address which owns the unbound NFT. - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the NFT being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param bindAddress The contract address handling asset ownership. - /// @param data Additional data sent with the `onERC721Bind` hook. - function bind( - address from, - address to, - uint256 tokenId, - uint256 bindId, - address bindAddress, - bytes calldata data - ) external; - - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// or owner of the delegated asset of `tokenId`. It also MUST throw if NFT - /// `tokenId` is not bound, if `from` is not the asset owner, or if `to` - /// is the zero address. After ownership transition, the function MUST - /// check if `bindAddress` is a valid contract (code size > 0), and if so, - /// call `onERC721Unbind` the contract, throwing if the wrong identifier is - /// returned (see "Binding Rules") or if the contract is invalid. - /// The function also MUST check if `to` is a valid contract, and if so, - /// call `onERC721Received`, throwing if the wrong identifier is returned. - /// On unbind completion, the function MUST emit both `Unbind` and IERC-721 - /// `Transfer` events to reflect delegated ownership change. - /// @param from The address which owns the asset the NFT is bound to. - /// @param to The address which will own the NFT once unbound. - /// @param tokenId The identifier of the NFT being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param data Additional data sent with the `onERC721Unbind` hook. - function unbind( - address from, - address to, - uint256 tokenId, - uint256 bindId, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Gets the asset identifier and address which a token is bound to. - /// @param tokenId The identifier of the NFT being queried. - /// @return The bound asset identifier and contract address. - function binderOf(uint256 tokenId) external returns (address, uint256); - - /// @notice Counts NFTs bound to asset `bindId` at address `bindAddress`. - /// @param bindId The identifier of the bound asset. - /// @param bindAddress The contract address handling bound asset ownership. - /// @return The total number of NFTs bound to the asset. - function boundBalanceOf(address bindAddress, uint256 bindId) external returns (uint256); - -} diff --git a/assets/eip-5700/interfaces/IERC721BindableErrors.sol b/assets/eip-5700/interfaces/IERC721BindableErrors.sol deleted file mode 100644 index 1124932..0000000 --- a/assets/eip-5700/interfaces/IERC721BindableErrors.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-721 Bindable Errors Interface -interface IERC721BindableErrors { - - /// @notice Bind already exists. - error BindExistent(); - - /// @notice Bind does not exist. - error BindNonexistent(); - - /// @notice Bind is not valid. - error BindInvalid(); - - /// @notice Bound asset or bound asset owner is not valid. - error BinderInvalid(); - -} diff --git a/assets/eip-5700/interfaces/IERC721Binder.sol b/assets/eip-5700/interfaces/IERC721Binder.sol deleted file mode 100644 index 93426e2..0000000 --- a/assets/eip-5700/interfaces/IERC721Binder.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -import {IERC721BinderErrors} from "./IERC721BinderErrors.sol"; - -/// @dev Note: the ERC-165 identifier for this interface is 0x2ac2d2bc. -interface IERC721Binder is IERC165, IERC721BinderErrors { - - /// @notice Handles the binding of an IERC721Bindable-compliant NFT. - /// @dev An IERC721Bindable-compliant smart contract MUST call this function - /// at the end of a `bind` after delegating ownership to the asset owner. - /// The function MUST revert if `to` is not the asset owner of `bindId` or - /// if asset `bindId` is not a valid asset. The function MUST revert if it - /// rejects the bind. If accepting the bind, the function MUST return - /// `bytes4(keccak256("onERC721Bind(address,address,address,uint256,uint256,bytes)"))` - /// Caller MUST revert the transaction if the above value is not returned. - /// Note: The contract address of the binding NFT is `msg.sender`. - /// @param operator The address responsible for initiating the bind. - /// @param from The address which owns the unbound NFT. - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the NFT being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param data Additional data sent along with no specified format. - /// @return `bytes4(keccak256("onERC721Bind(address,address,address,uint256,uint256,bytes)"))` - function onERC721Bind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 bindId, - bytes calldata data - ) external returns (bytes4); - - /// @notice Handles the unbinding of an IERC721Bindable-compliant NFT. - /// @dev An IERC721Bindable-compliant smart contract MUST call this function - /// at the end of an `unbind` after revoking delegated asset ownership. - /// The function MUST revert if `from` is not the asset owner of `bindId` - /// or if `bindId` is not a valid asset. The function MUST revert if it - /// rejects the unbind. If accepting the unbind, the function MUST return - /// `bytes4(keccak256("onERC721Unbind(address,address,address,uint256,uint256,bytes)"))` - /// Caller MUST revert the transaction if the above value is not returned. - /// Note: The contract address of the unbinding NFT is `msg.sender`. - /// @param from The address which owns the asset the NFT is bound to. - /// @param to The address which will own the NFT once unbound. - /// @param tokenId The identifier of the NFT being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param data Additional data with no specified format. - /// @return `bytes4(keccak256("onERC721Unbind(address,address,address,uint256,uint256,bytes)"))` - function onERC721Unbind( - address operator, - address from, - address to, - uint256 tokenId, - uint256 bindId, - bytes calldata data - ) external returns (bytes4); - - /// @notice Gets the owner address of the asset represented by id `bindId`. - /// @dev Queries for assets assigned to the zero address MUST throw. - /// @param bindId The identifier of the asset whose owner is being queried. - /// @return The address of the owner of the asset. - function ownerOf(uint256 bindId) external view returns (address); - - /// @notice Checks if an operator can act on behalf of an asset owner. - /// @param owner The address that owns an asset. - /// @param operator The address that acts on behalf of owner `owner`. - /// @return True if `operator` can act on behalf of `owner`, else False. - function isApprovedForAll(address owner, address operator) external view returns (bool); - -} diff --git a/assets/eip-5700/interfaces/IERC721BinderErrors.sol b/assets/eip-5700/interfaces/IERC721BinderErrors.sol deleted file mode 100644 index c949e6e..0000000 --- a/assets/eip-5700/interfaces/IERC721BinderErrors.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-721 Binder Errors Interface -interface IERC721BinderErrors { - - /// @notice Asset binding already exists. - error BindExistent(); - - /// @notice Asset binding is not valid. - error BindInvalid(); - - /// @notice Asset binding does not exist. - error BindNonexistent(); - - /// @notice Originating address does not own the asset. - error OwnerInvalid(); - - /// @notice Receiving address cannot be the zero address. - error ReceiverInvalid(); - - /// @notice Sender is not NFT owner, approved address, or owner operator. - error SenderUnauthorized(); - -} diff --git a/assets/eip-5700/interfaces/IERC721Errors.sol b/assets/eip-5700/interfaces/IERC721Errors.sol deleted file mode 100644 index 2fc6494..0000000 --- a/assets/eip-5700/interfaces/IERC721Errors.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-721 Errors Interface -interface IERC721Errors { - - /// @notice Originating address does not own the NFT. - error OwnerInvalid(); - - /// @notice Receiving address cannot be the zero address. - error ReceiverInvalid(); - - /// @notice Receiving contract does not implement the ERC-721 wallet interface. - error SafeTransferUnsupported(); - - /// @notice Sender is not NFT owner, approved address, or owner operator. - error SenderUnauthorized(); - - /// @notice NFT supply has hit maximum capacity. - error SupplyMaxCapacity(); - - /// @notice Token has already minted. - error TokenAlreadyMinted(); - - /// @notice NFT does not exist. - error TokenNonExistent(); - -} - diff --git a/assets/eip-5725/README.md b/assets/eip-5725/README.md deleted file mode 100644 index 65bfb82..0000000 --- a/assets/eip-5725/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# EIP-5725: Transferrable Vesting NFT - Reference Implementation -This repository serves as a reference implementation for **EIP-5725 Transferrable Vesting NFT Standard**. A Non-Fungible Token (NFT) standard used to vest tokens (ERC-20 or otherwise) over a vesting release curve. - -## Contents -- [EIP-5725 Specification](./contracts/IERC5725.sol): Interface and definitions for the EIP-5725 specification. -- [ERC-5725 Implementation (abstract)](./contracts/ERC5725.sol): ERC-5725 contract which can be extended to implement the specification. -- [VestingNFT Implementation](./contracts/reference/LinearVestingNFT.sol): Full ERC-5725 implementation using cliff vesting curve. -- [LinearVestingNFT Implementation](./contracts/reference/VestingNFT.sol): Full ERC-5725 implementation using linear vesting curve. \ No newline at end of file diff --git a/assets/eip-5725/contracts/ERC5725.sol b/assets/eip-5725/contracts/ERC5725.sol deleted file mode 100644 index ff030b1..0000000 --- a/assets/eip-5725/contracts/ERC5725.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "./IERC5725.sol"; - -abstract contract ERC5725 is IERC5725, ERC721 { - using SafeERC20 for IERC20; - - /// @dev mapping for claimed payouts - mapping(uint256 => uint256) /*tokenId*/ /*claimed*/ - internal _payoutClaimed; - - /** - * @notice Checks if the tokenId exists and its valid - * @param tokenId The NFT token id - */ - modifier validToken(uint256 tokenId) { - require(_exists(tokenId), "ERC5725: invalid token ID"); - _; - } - - /** - * @dev See {IERC5725}. - */ - function claim(uint256 tokenId) external override(IERC5725) validToken(tokenId) { - require(ownerOf(tokenId) == msg.sender, "Not owner of NFT"); - uint256 amountClaimed = claimablePayout(tokenId); - require(amountClaimed > 0, "ERC5725: No pending payout"); - - emit PayoutClaimed(tokenId, msg.sender, amountClaimed); - - _payoutClaimed[tokenId] += amountClaimed; - IERC20(payoutToken(tokenId)).safeTransfer(msg.sender, amountClaimed); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayout(uint256 tokenId) public view override(IERC5725) returns (uint256 payout) { - return vestedPayoutAtTime(tokenId, block.timestamp); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) - public - view - virtual - override(IERC5725) - returns (uint256 payout); - - /** - * @dev See {IERC5725}. - */ - function vestingPayout(uint256 tokenId) - public - view - override(IERC5725) - validToken(tokenId) - returns (uint256 payout) - { - return _payout(tokenId) - vestedPayout(tokenId); - } - - /** - * @dev See {IERC5725}. - */ - function claimablePayout(uint256 tokenId) - public - view - override(IERC5725) - validToken(tokenId) - returns (uint256 payout) - { - return vestedPayout(tokenId) - _payoutClaimed[tokenId]; - } - - /** - * @dev See {IERC5725}. - */ - function claimedPayout(uint256 tokenId) - public - view - override(IERC5725) - validToken(tokenId) - returns (uint256 payout) - { - return _payoutClaimed[tokenId]; - } - - /** - * @dev See {IERC5725}. - */ - function vestingPeriod(uint256 tokenId) - public - view - override(IERC5725) - validToken(tokenId) - returns (uint256 vestingStart, uint256 vestingEnd) - { - return (_startTime(tokenId), _endTime(tokenId)); - } - - /** - * @dev See {IERC5725}. - */ - function payoutToken(uint256 tokenId) public view override(IERC5725) validToken(tokenId) returns (address token) { - return _payoutToken(tokenId); - } - - /** - * @dev See {IERC165-supportsInterface}. - * IERC5725 interfaceId = 0x7c89676d - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721, IERC165) - returns (bool supported) - { - return interfaceId == type(IERC5725).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * @dev Internal function to get the payout token of a given vesting NFT - * - * @param tokenId on which to check the payout token address - * @return address payout token address - */ - function _payoutToken(uint256 tokenId) internal view virtual returns (address); - - /** - * @dev Internal function to get the total payout of a given vesting NFT. - * @dev This is the total that will be paid out to the NFT owner, including historical tokens. - * - * @param tokenId to check - * @return uint256 the total payout of a given vesting NFT - */ - function _payout(uint256 tokenId) internal view virtual returns (uint256); - - /** - * @dev Internal function to get the start time of a given vesting NFT - * - * @param tokenId to check - * @return uint256 the start time in epoch timestamp - */ - function _startTime(uint256 tokenId) internal view virtual returns (uint256); - - /** - * @dev Internal function to get the end time of a given vesting NFT - * - * @param tokenId to check - * @return uint256 the end time in epoch timestamp - */ - function _endTime(uint256 tokenId) internal view virtual returns (uint256); -} diff --git a/assets/eip-5725/contracts/IERC5725.sol b/assets/eip-5725/contracts/IERC5725.sol deleted file mode 100644 index d7b0196..0000000 --- a/assets/eip-5725/contracts/IERC5725.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/** - * @title Non-Fungible Vesting Token Standard - * @notice A non-fungible token standard used to vest tokens (ERC-20 or otherwise) over a vesting release curve - * scheduled using timestamps. - * @dev Because this standard relies on timestamps for the vesting schedule, it's important to keep track of the - * tokens claimed per Vesting NFT so that a user cannot withdraw more tokens than alloted for a specific Vesting NFT. - */ -interface IERC5725 is IERC721 { - /** - * This event is emitted when the payout is claimed through the claim function - * @param tokenId the NFT tokenId of the assets being claimed. - * @param recipient The address which is receiving the payout. - * @param claimAmount The amount of tokens being claimed. - */ - event PayoutClaimed(uint256 indexed tokenId, address indexed recipient, uint256 claimAmount); - - /** - * @notice Claim the pending payout for the NFT - * @dev MUST grant the claimablePayout value at the time of claim being called - * MUST revert if not called by the token owner or approved users - * MUST emit PayoutClaimed - * SHOULD revert if there is nothing to claim - * @param tokenId The NFT token id - */ - function claim(uint256 tokenId) external; - - /** - * @notice Number of tokens for the NFT which have been claimed at the current timestamp - * @param tokenId The NFT token id - * @return payout The total amount of payout tokens claimed for this NFT - */ - function claimedPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Number of tokens for the NFT which can be claimed at the current timestamp - * @dev It is RECOMMENDED that this is calculated as the `vestedPayout()` subtracted from `payoutClaimed()`. - * @param tokenId The NFT token id - * @return payout The amount of unlocked payout tokens for the NFT which have not yet been claimed - */ - function claimablePayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Total amount of tokens which have been vested at the current timestamp. - * This number also includes vested tokens which have been claimed. - * @dev It is RECOMMENDED that this function calls `vestedPayoutAtTime` with - * `block.timestamp` as the `timestamp` parameter. - * @param tokenId The NFT token id - * @return payout Total amount of tokens which have been vested at the current timestamp. - */ - function vestedPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Total amount of vested tokens at the provided timestamp. - * This number also includes vested tokens which have been claimed. - * @dev `timestamp` MAY be both in the future and in the past. - * Zero MUST be returned if the timestamp is before the token was minted. - * @param tokenId The NFT token id - * @param timestamp The timestamp to check on, can be both in the past and the future - * @return payout Total amount of tokens which have been vested at the provided timestamp - */ - function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) external view returns (uint256 payout); - - /** - * @notice Number of tokens for an NFT which are currently vesting. - * @dev The sum of vestedPayout and vestingPayout SHOULD always be the total payout. - * @param tokenId The NFT token id - * @return payout The number of tokens for the NFT which are vesting until a future date. - */ - function vestingPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice The start and end timestamps for the vesting of the provided NFT - * MUST return the timestamp where no further increase in vestedPayout occurs for `vestingEnd`. - * @param tokenId The NFT token id - * @return vestingStart The beginning of the vesting as a unix timestamp - * @return vestingEnd The ending of the vesting as a unix timestamp - */ - function vestingPeriod(uint256 tokenId) external view returns (uint256 vestingStart, uint256 vestingEnd); - - /** - * @notice Token which is used to pay out the vesting claims - * @param tokenId The NFT token id - * @return token The token which is used to pay out the vesting claims - */ - function payoutToken(uint256 tokenId) external view returns (address token); -} diff --git a/assets/eip-5725/contracts/mocks/ERC20Mock.sol b/assets/eip-5725/contracts/mocks/ERC20Mock.sol deleted file mode 100644 index c006a24..0000000 --- a/assets/eip-5725/contracts/mocks/ERC20Mock.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.17; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract ERC20Mock is ERC20 { - uint8 private _decimals; - - constructor( - uint256 supply_, - uint8 decimals_, - string memory name_, - string memory symbol_ - ) ERC20(name_, symbol_) { - _mint(msg.sender, supply_); - _decimals = decimals_; - } - - function mint(uint256 amount, address to) public { - _mint(to, amount); - } - - function decimals() public view virtual override returns (uint8) { - return _decimals; - } -} diff --git a/assets/eip-5725/contracts/reference/LinearVestingNFT.sol b/assets/eip-5725/contracts/reference/LinearVestingNFT.sol deleted file mode 100644 index a87fd9e..0000000 --- a/assets/eip-5725/contracts/reference/LinearVestingNFT.sol +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "../ERC5725.sol"; - -contract LinearVestingNFT is ERC5725 { - using SafeERC20 for IERC20; - - struct VestDetails { - IERC20 payoutToken; /// @dev payout token - uint256 payout; /// @dev payout token remaining to be paid - uint128 startTime; /// @dev when vesting starts - uint128 endTime; /// @dev when vesting end - uint128 cliff; /// @dev duration in seconds of the cliff in which tokens will be begin releasing - } - mapping(uint256 => VestDetails) public vestDetails; /// @dev maps the vesting data with tokenIds - - /// @dev tracker of current NFT id - uint256 private _tokenIdTracker; - - /** - * @dev See {IERC5725}. - */ - constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - - /** - * @notice Creates a new vesting NFT and mints it - * @dev Token amount should be approved to be transferred by this contract before executing create - * @param to The recipient of the NFT - * @param amount The total assets to be locked over time - * @param startTime When the vesting starts in epoch timestamp - * @param duration The vesting duration in seconds - * @param cliff The cliff duration in seconds - * @param token The ERC20 token to vest over time - */ - function create( - address to, - uint256 amount, - uint128 startTime, - uint128 duration, - uint128 cliff, - IERC20 token - ) public virtual { - require(startTime >= block.timestamp, "startTime cannot be on the past"); - require(to != address(0), "to cannot be address 0"); - require(cliff <= duration, "duration needs to be more than cliff"); - - uint256 newTokenId = _tokenIdTracker; - - vestDetails[newTokenId] = VestDetails({ - payoutToken: token, - payout: amount, - startTime: startTime, - endTime: startTime + duration, - cliff: startTime + cliff - }); - - _tokenIdTracker++; - _mint(to, newTokenId); - IERC20(payoutToken(newTokenId)).safeTransferFrom(msg.sender, address(this), amount); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) - public - view - override(ERC5725) - validToken(tokenId) - returns (uint256 payout) - { - if (timestamp < _cliff(tokenId)) { - return 0; - } - if (timestamp > _endTime(tokenId)) { - return _payout(tokenId); - } - return (_payout(tokenId) * (timestamp - _startTime(tokenId))) / (_endTime(tokenId) - _startTime(tokenId)); - } - - /** - * @dev See {ERC5725}. - */ - function _payoutToken(uint256 tokenId) internal view override returns (address) { - return address(vestDetails[tokenId].payoutToken); - } - - /** - * @dev See {ERC5725}. - */ - function _payout(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].payout; - } - - /** - * @dev See {ERC5725}. - */ - function _startTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].startTime; - } - - /** - * @dev See {ERC5725}. - */ - function _endTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].endTime; - } - - /** - * @dev Internal function to get the cliff time of a given linear vesting NFT - * - * @param tokenId to check - * @return uint256 the cliff time in seconds - */ - function _cliff(uint256 tokenId) internal view returns (uint256) { - return vestDetails[tokenId].cliff; - } -} diff --git a/assets/eip-5725/contracts/reference/VestingNFT.sol b/assets/eip-5725/contracts/reference/VestingNFT.sol deleted file mode 100644 index 1b50d85..0000000 --- a/assets/eip-5725/contracts/reference/VestingNFT.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "../ERC5725.sol"; - -contract VestingNFT is ERC5725 { - using SafeERC20 for IERC20; - - struct VestDetails { - IERC20 payoutToken; /// @dev payout token - uint256 payout; /// @dev payout token remaining to be paid - uint128 startTime; /// @dev when vesting starts - uint128 endTime; /// @dev when vesting end - } - mapping(uint256 => VestDetails) public vestDetails; /// @dev maps the vesting data with tokenIds - - /// @dev tracker of current NFT id - uint256 private _tokenIdTracker; - - /** - * @dev Initializes the contract by setting a `name` and a `symbol` to the token. - */ - constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - - /** - * @notice Creates a new vesting NFT and mints it - * @dev Token amount should be approved to be transferred by this contract before executing create - * @param to The recipient of the NFT - * @param amount The total assets to be locked over time - * @param releaseTimestamp When the full amount of tokens get released - * @param token The ERC20 token to vest over time - */ - function create( - address to, - uint256 amount, - uint128 releaseTimestamp, - IERC20 token - ) public virtual { - require(to != address(0), "to cannot be address 0"); - require(releaseTimestamp > block.timestamp, "release must be in future"); - - uint256 newTokenId = _tokenIdTracker; - - vestDetails[newTokenId] = VestDetails({ - payoutToken: token, - payout: amount, - startTime: uint128(block.timestamp), - endTime: releaseTimestamp - }); - - _tokenIdTracker++; - _mint(to, newTokenId); - IERC20(payoutToken(newTokenId)).safeTransferFrom(msg.sender, address(this), amount); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) - public - view - override(ERC5725) - validToken(tokenId) - returns (uint256 payout) - { - if (timestamp >= _endTime(tokenId)) { - return _payout(tokenId); - } - return 0; - } - - /** - * @dev See {ERC5725}. - */ - function _payoutToken(uint256 tokenId) internal view override returns (address) { - return address(vestDetails[tokenId].payoutToken); - } - - /** - * @dev See {ERC5725}. - */ - function _payout(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].payout; - } - - /** - * @dev See {ERC5725}. - */ - function _startTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].startTime; - } - - /** - * @dev See {ERC5725}. - */ - function _endTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].endTime; - } -} diff --git a/assets/eip-5725/test/LinearVestingNFT.test.ts b/assets/eip-5725/test/LinearVestingNFT.test.ts deleted file mode 100644 index 7d1b23c..0000000 --- a/assets/eip-5725/test/LinearVestingNFT.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { ethers } from 'hardhat' -import { Signer } from 'ethers' -import { expect } from 'chai' -import { increaseTime } from './helpers/time' -// typechain -import { - ERC20Mock__factory, - ERC20Mock, - LinearVestingNFT__factory, - LinearVestingNFT, -} from '../typechain-types' - -const testValues = { - payout: '1000000000', - lockTime: 60, - buffer: 10, - totalLock: 70, -} - -describe('LinearVestingNFT', function () { - let accounts: Signer[] - let linearVestingNFT: LinearVestingNFT - let mockToken: ERC20Mock - let receiverAccount: string - let unlockTime: number - - beforeEach(async function () { - const LinearVestingNFT = (await ethers.getContractFactory( - 'LinearVestingNFT' - )) as LinearVestingNFT__factory - linearVestingNFT = await LinearVestingNFT.deploy('LinearVestingNFT', 'TLV') - await linearVestingNFT.deployed() - - const ERC20Mock = (await ethers.getContractFactory( - 'ERC20Mock' - )) as ERC20Mock__factory - mockToken = await ERC20Mock.deploy( - '1000000000000000000000', - 18, - 'LockedToken', - 'LOCK' - ) - await mockToken.deployed() - await mockToken.approve(linearVestingNFT.address, '1000000000000000000000') - - accounts = await ethers.getSigners() - receiverAccount = await accounts[1].getAddress() - unlockTime = await createVestingNft( - linearVestingNFT, - receiverAccount, - mockToken - ) - }) - - it('Returns a valid vested payout', async function () { - // TODO: More extensive testing of linear vesting functionality - const totalPayout = await linearVestingNFT.vestedPayoutAtTime(0, unlockTime) - expect(await linearVestingNFT.vestedPayout(0)).to.equal(0) - await increaseTime(testValues.totalLock) - expect(await linearVestingNFT.vestedPayout(0)).to.equal(totalPayout) - }) - - it('Reverts when creating to account 0', async function () { - const latestBlock = await ethers.provider.getBlock('latest') - await expect( - linearVestingNFT.create( - '0x0000000000000000000000000000000000000000', - testValues.payout, - latestBlock.timestamp + testValues.buffer, - testValues.lockTime, - 0, - mockToken.address - ) - ).to.revertedWith('to cannot be address 0') - }) - - it('Reverts when creating to past start date 0', async function () { - await expect( - linearVestingNFT.create( - receiverAccount, - testValues.payout, - 0, - testValues.lockTime, - 0, - mockToken.address - ) - ).to.revertedWith('startTime cannot be on the past') - }) - - it('Reverts when duration is less than cliff', async function () { - const latestBlock = await ethers.provider.getBlock('latest') - await expect( - linearVestingNFT.create( - receiverAccount, - testValues.payout, - latestBlock.timestamp + testValues.buffer, - testValues.lockTime, - 100, - mockToken.address - ) - ).to.revertedWith('duration needs to be more than cliff') - }) -}) - -async function createVestingNft( - linearVestingNFT: LinearVestingNFT, - receiverAccount: string, - mockToken: ERC20Mock -) { - const latestBlock = await ethers.provider.getBlock('latest') - const unlockTime = - latestBlock.timestamp + testValues.lockTime + testValues.buffer - const txReceipt = await linearVestingNFT.create( - receiverAccount, - testValues.payout, - latestBlock.timestamp + testValues.buffer, - testValues.lockTime, - 0, - mockToken.address - ) - await txReceipt.wait() - return unlockTime -} diff --git a/assets/eip-5725/test/VestingNFT.test.ts b/assets/eip-5725/test/VestingNFT.test.ts deleted file mode 100644 index cc2893e..0000000 --- a/assets/eip-5725/test/VestingNFT.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { ethers } from 'hardhat' -import { Signer } from 'ethers' -import { expect } from 'chai' -import { increaseTime } from './helpers/time' -// typechain -import { ERC20Mock, VestingNFT } from '../typechain-types' - -const testValues = { - payout: '1000000000', - lockTime: 60, -} - -describe('VestingNFT', function () { - let accounts: Signer[] - let vestingNFT: VestingNFT - let mockToken: ERC20Mock - let receiverAccount: string - let unlockTime: number - - beforeEach(async function () { - const VestingNFT = await ethers.getContractFactory('VestingNFT') - vestingNFT = await VestingNFT.deploy('VestingNFT', 'TLV') - await vestingNFT.deployed() - - const ERC20Mock = await ethers.getContractFactory('ERC20Mock') - mockToken = await ERC20Mock.deploy( - '1000000000000000000000', - 18, - 'LockedToken', - 'LOCK' - ) - await mockToken.deployed() - await mockToken.approve(vestingNFT.address, '1000000000000000000000') - - accounts = await ethers.getSigners() - receiverAccount = await accounts[1].getAddress() - unlockTime = await createVestingNft(vestingNFT, receiverAccount, mockToken) - }) - - it('Supports ERC721 and IERC5725 interfaces', async function () { - expect(await vestingNFT.supportsInterface('0x80ac58cd')).to.equal(true) - - /** - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified - * // Solidity export interface id: - * bytes4 public constant IID_ITEST = type(IERC5725).interfaceId; - * // Pull out the interfaceId in tests - * const interfaceId = await vestingNFT.IID_ITEST(); - */ - // Vesting NFT Interface ID - expect(await vestingNFT.supportsInterface('0xf8600f8b')).to.equal(true) - }) - - it('Returns a valid vested payout', async function () { - const totalPayout = await vestingNFT.vestedPayoutAtTime(0, unlockTime) - expect(await vestingNFT.vestedPayout(0)).to.equal(0) - await increaseTime(testValues.lockTime) - expect(await vestingNFT.vestedPayout(0)).to.equal(totalPayout) - }) - - it('Reverts with invalid ID', async function () { - await expect(vestingNFT.vestedPayout(1)).to.revertedWith( - 'VestingNFT: invalid token ID' - ) - await expect(vestingNFT.vestedPayoutAtTime(1, unlockTime)).to.revertedWith( - 'VestingNFT: invalid token ID' - ) - await expect(vestingNFT.vestingPayout(1)).to.revertedWith( - 'VestingNFT: invalid token ID' - ) - await expect(vestingNFT.claimablePayout(1)).to.revertedWith( - 'VestingNFT: invalid token ID' - ) - await expect(vestingNFT.vestingPeriod(1)).to.revertedWith( - 'VestingNFT: invalid token ID' - ) - await expect(vestingNFT.payoutToken(1)).to.revertedWith( - 'VestingNFT: invalid token ID' - ) - await expect(vestingNFT.claim(1)).to.revertedWith( - 'VestingNFT: invalid token ID' - ) - // NOTE: Removed claimTo from spec - // await expect(vestingNFT.claimTo(1, receiverAccount)).to.revertedWith( - // "VestingNFT: invalid token ID" - // ); - }) - - it('Returns a valid pending payout', async function () { - expect(await vestingNFT.vestingPayout(0)).to.equal(testValues.payout) - }) - - it('Returns a valid releasable payout', async function () { - const totalPayout = await vestingNFT.vestedPayoutAtTime(0, unlockTime) - expect(await vestingNFT.claimablePayout(0)).to.equal(0) - await increaseTime(testValues.lockTime) - expect(await vestingNFT.claimablePayout(0)).to.equal(totalPayout) - }) - - it('Returns a valid vesting period', async function () { - const vestingPeriod = await vestingNFT.vestingPeriod(0) - expect(vestingPeriod.vestingEnd).to.equal(unlockTime) - }) - - it('Returns a valid payout token', async function () { - expect(await vestingNFT.payoutToken(0)).to.equal(mockToken.address) - }) - - it('Is able to claim', async function () { - const connectedVestingNft = vestingNFT.connect(accounts[1]) - await increaseTime(testValues.lockTime) - const txReceipt = await connectedVestingNft.claim(0) - await txReceipt.wait() - expect(await mockToken.balanceOf(receiverAccount)).to.equal( - testValues.payout - ) - }) - - it('Reverts claim when payout is 0', async function () { - const connectedVestingNft = vestingNFT.connect(accounts[1]) - await expect(connectedVestingNft.claim(0)).to.revertedWith( - 'VestingNFT: No pending payout' - ) - }) - - it('Reverts claim when payout is not from owner', async function () { - const connectedVestingNft = vestingNFT.connect(accounts[2]) - await expect(connectedVestingNft.claim(0)).to.revertedWith( - 'Not owner of NFT' - ) - }) - - // NOTE: Removed claimTo from spec - // it("Is able to claim to other account", async function () { - // const connectedVestingNft = vestingNFT.connect(accounts[1]); - // const otherReceiverAddress = await accounts[2].getAddress(); - // await increaseTime(testValues.lockTime); - // const txReceipt = await connectedVestingNft.claimTo( - // 0, - // otherReceiverAddress - // ); - // await txReceipt.wait(); - // expect(await mockToken.balanceOf(otherReceiverAddress)).to.equal( - // testValues.payout - // ); - // }); - - it('Reverts when creating to account 0', async function () { - await expect( - vestingNFT.create( - '0x0000000000000000000000000000000000000000', - testValues.payout, - unlockTime, - mockToken.address - ) - ).to.revertedWith('to cannot be address 0') - }) -}) - -async function createVestingNft( - vestingNFT: VestingNFT, - receiverAccount: string, - mockToken: ERC20Mock -) { - const latestBlock = await ethers.provider.getBlock('latest') - const unlockTime = latestBlock.timestamp + testValues.lockTime - const txReceipt = await vestingNFT.create( - receiverAccount, - testValues.payout, - unlockTime, - mockToken.address - ) - await txReceipt.wait() - return unlockTime -} diff --git a/assets/eip-5725/test/helpers/time.ts b/assets/eip-5725/test/helpers/time.ts deleted file mode 100644 index 6e99622..0000000 --- a/assets/eip-5725/test/helpers/time.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ethers } from 'hardhat' - -async function mineNBlocks(n: number) { - for (let index = 0; index < n; index++) { - await ethers.provider.send('evm_mine', []) - } -} - -export async function increaseTime(seconds: number) { - await ethers.provider.send('evm_increaseTime', [seconds]) - await mineNBlocks(1) -} diff --git a/assets/eip-5773/contracts/IERC5773.sol b/assets/eip-5773/contracts/IERC5773.sol deleted file mode 100644 index d864585..0000000 --- a/assets/eip-5773/contracts/IERC5773.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5773 { - event AssetSet(uint64 assetId); - - event AssetAddedToTokens( - uint256[] tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetAccepted( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetRejected(uint256 indexed tokenId, uint64 indexed assetId); - - event AssetPrioritySet(uint256 indexed tokenId); - - event ApprovalForAssets( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - event ApprovalForAllForAssets( - address indexed owner, - address indexed operator, - bool approved - ); - - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAllAssets(uint256 tokenId, uint256 maxRejections) external; - - function setPriority( - uint256 tokenId, - uint64[] calldata priorities - ) external; - - function getActiveAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getPendingAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getActiveAssetPriorities( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) external view returns (uint64); - - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) external view returns (string memory); - - function approveForAssets(address to, uint256 tokenId) external; - - function getApprovedForAssets( - uint256 tokenId - ) external view returns (address); - - function setApprovalForAllForAssets( - address operator, - bool approved - ) external; - - function isApprovedForAllForAssets( - address owner, - address operator - ) external view returns (bool); -} diff --git a/assets/eip-5773/contracts/MultiAssetToken.sol b/assets/eip-5773/contracts/MultiAssetToken.sol deleted file mode 100644 index 089fc83..0000000 --- a/assets/eip-5773/contracts/MultiAssetToken.sol +++ /dev/null @@ -1,684 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -import "./IERC5773.sol"; -import "./library/MultiAssetLib.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; - -contract MultiAssetToken is Context, IERC721, IERC5773 { - using MultiAssetLib for uint256; - using MultiAssetLib for uint64[]; - using MultiAssetLib for uint128[]; - using Address for address; - using Strings for uint256; - - // Token name - string private _name; - - // Token symbol - string private _symbol; - - // Mapping from token ID to owner address - mapping(uint256 => address) private _owners; - - // Mapping owner address to token count - mapping(address => uint256) private _balances; - - // Mapping from token ID to approved address - mapping(uint256 => address) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // Mapping from token ID to approved address for assets - mapping(uint256 => address) internal _tokenApprovalsForAssets; - - // Mapping from owner to operator approvals for assets - mapping(address => mapping(address => bool)) - internal _operatorApprovalsForAssets; - - //mapping of uint64 Ids to asset object - mapping(uint64 => string) internal _assets; - - //mapping of tokenId to new asset, to asset to be replaced - mapping(uint256 => mapping(uint64 => uint64)) private _assetReplacements; - - //mapping of tokenId to all assets - mapping(uint256 => uint64[]) internal _activeAssets; - - //mapping of tokenId to an array of asset priorities - mapping(uint256 => uint64[]) internal _activeAssetPriorities; - - //Double mapping of tokenId to active assets - mapping(uint256 => mapping(uint64 => bool)) private _tokenAssets; - - //mapping of tokenId to all assets by priority - mapping(uint256 => uint64[]) internal _pendingAssets; - - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - } - - //////////////////////////////////////// - // ERC-721 COMPLIANCE - //////////////////////////////////////// - - function supportsInterface(bytes4 interfaceId) public view returns (bool) { - return - interfaceId == type(IERC5773).interfaceId || - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC165).interfaceId; - } - - function balanceOf( - address owner - ) public view virtual override returns (uint256) { - require( - owner != address(0), - "ERC721: address zero is not a valid owner" - ); - return _balances[owner]; - } - - function ownerOf( - uint256 tokenId - ) public view virtual override returns (address) { - address owner = _owners[tokenId]; - require( - owner != address(0), - "ERC721: owner query for nonexistent token" - ); - return owner; - } - - function name() public view virtual returns (string memory) { - return _name; - } - - function symbol() public view virtual returns (string memory) { - return _symbol; - } - - function approve(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - require(to != owner, "MultiAsset: approval to current owner"); - require( - _msgSender() == owner || isApprovedForAll(owner, _msgSender()), - "MultiAsset: approve caller is not owner nor approved for all" - ); - - _approve(to, tokenId); - } - - function approveForAssets(address to, uint256 tokenId) external virtual { - address owner = ownerOf(tokenId); - require(to != owner, "MultiAsset: approval to current owner"); - require( - _msgSender() == owner || - isApprovedForAllForAssets(owner, _msgSender()), - "MultiAsset: approve caller is not owner nor approved for all" - ); - _approveForAssets(to, tokenId); - } - - function getApproved( - uint256 tokenId - ) public view virtual override returns (address) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - - return _tokenApprovals[tokenId]; - } - - function getApprovedForAssets( - uint256 tokenId - ) public view virtual returns (address) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - return _tokenApprovalsForAssets[tokenId]; - } - - function setApprovalForAll( - address operator, - bool approved - ) public virtual override { - _setApprovalForAll(_msgSender(), operator, approved); - } - - function isApprovedForAll( - address owner, - address operator - ) public view virtual override returns (bool) { - return _operatorApprovals[owner][operator]; - } - - function setApprovalForAllForAssets( - address operator, - bool approved - ) public virtual override { - _setApprovalForAllForAssets(_msgSender(), operator, approved); - } - - function isApprovedForAllForAssets( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovalsForAssets[owner][operator]; - } - - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - //solhint-disable-next-line max-line-length - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "MultiAsset: transfer caller is not owner nor approved" - ); - - _transfer(from, to, tokenId); - } - - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - safeTransferFrom(from, to, tokenId, ""); - } - - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual override { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "MultiAsset: transfer caller is not owner nor approved" - ); - _safeTransfer(from, to, tokenId, data); - } - - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _transfer(from, to, tokenId); - require( - _checkOnERC721Received(from, to, tokenId, data), - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - } - - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _owners[tokenId] != address(0); - } - - function _isApprovedOrOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - address owner = ownerOf(tokenId); - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - function _isApprovedForAssetsOrOwner( - address user, - uint256 tokenId - ) internal view virtual returns (bool) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - address owner = ownerOf(tokenId); - return (user == owner || - isApprovedForAllForAssets(owner, user) || - getApprovedForAssets(tokenId) == user); - } - - function _safeMint(address to, uint256 tokenId) internal virtual { - _safeMint(to, tokenId, ""); - } - - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _mint(to, tokenId); - require( - _checkOnERC721Received(address(0), to, tokenId, data), - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - } - - function _mint(address to, uint256 tokenId) internal virtual { - require(to != address(0), "MultiAsset: mint to the zero address"); - require(!_exists(tokenId), "MultiAsset: token already minted"); - - _beforeTokenTransfer(address(0), to, tokenId); - - _balances[to] += 1; - _owners[tokenId] = to; - - emit Transfer(address(0), to, tokenId); - - _afterTokenTransfer(address(0), to, tokenId); - } - - function _burn(uint256 tokenId) internal virtual { - // WARNING: If you intend to allow the reminting of a burned token, you - // might want to clean the assets for the token, that is: - // _pendingAssets, _activeAssets, _assetReplacements - // _activeAssetPriorities and _tokenAssets. - address owner = ownerOf(tokenId); - - _beforeTokenTransfer(owner, address(0), tokenId); - - // Clear approvals - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - - _balances[owner] -= 1; - delete _owners[tokenId]; - - emit Transfer(owner, address(0), tokenId); - - _afterTokenTransfer(owner, address(0), tokenId); - } - - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual { - require( - ownerOf(tokenId) == from, - "MultiAsset: transfer from incorrect owner" - ); - require(to != address(0), "MultiAsset: transfer to the zero address"); - - _beforeTokenTransfer(from, to, tokenId); - - // Clear approvals from the previous owner - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - - _balances[from] -= 1; - _balances[to] += 1; - _owners[tokenId] = to; - - emit Transfer(from, to, tokenId); - - _afterTokenTransfer(from, to, tokenId); - } - - function _approve(address to, uint256 tokenId) internal virtual { - _tokenApprovals[tokenId] = to; - emit Approval(ownerOf(tokenId), to, tokenId); - } - - function _approveForAssets(address to, uint256 tokenId) internal virtual { - _tokenApprovalsForAssets[tokenId] = to; - emit ApprovalForAssets(ownerOf(tokenId), to, tokenId); - } - - function _setApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "MultiAsset: approve to caller"); - _operatorApprovals[owner][operator] = approved; - emit ApprovalForAll(owner, operator, approved); - } - - function _setApprovalForAllForAssets( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "MultiAsset: approve to caller"); - _operatorApprovalsForAssets[owner][operator] = approved; - emit ApprovalForAllForAssets(owner, operator, approved); - } - - function _checkOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory data - ) private returns (bool) { - if (to.isContract()) { - try - IERC721Receiver(to).onERC721Received( - _msgSender(), - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert( - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - //////////////////////////////////////// - // ASSETS - //////////////////////////////////////// - - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external virtual { - require( - index < _pendingAssets[tokenId].length, - "MultiAsset: index out of bounds" - ); - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - require( - assetId == _pendingAssets[tokenId][index], - "MultiAsset: Unexpected asset" - ); - - _beforeAcceptAsset(tokenId, index, assetId); - uint64 replacesId = _assetReplacements[tokenId][assetId]; - uint256 replaceIndex; - bool replacefound; - if (replacesId != uint64(0)) - (replaceIndex, replacefound) = _activeAssets[tokenId].indexOf( - replacesId - ); - - if (replacefound) { - // We don't want to remove and then push a new asset. - // This way we also keep the priority of the original resource - _activeAssets[tokenId][replaceIndex] = assetId; - delete _tokenAssets[tokenId][replacesId]; - } else { - // We use the current size as next priority, by default priorities would be [0,1,2...] - _activeAssetPriorities[tokenId].push( - uint64(_activeAssets[tokenId].length) - ); - _activeAssets[tokenId].push(assetId); - replacesId = uint64(0); - } - _pendingAssets[tokenId].removeItemByIndex(index); - delete _assetReplacements[tokenId][assetId]; - - emit AssetAccepted(tokenId, assetId, replacesId); - _afterAcceptAsset(tokenId, index, assetId); - } - - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external virtual { - require( - index < _pendingAssets[tokenId].length, - "MultiAsset: index out of bounds" - ); - require( - _pendingAssets[tokenId].length > index, - "MultiAsset: Pending asset index out of range" - ); - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - - _beforeRejectAsset(tokenId, index, assetId); - _pendingAssets[tokenId].removeItemByIndex(index); - delete _tokenAssets[tokenId][assetId]; - delete _assetReplacements[tokenId][assetId]; - - emit AssetRejected(tokenId, assetId); - _afterRejectAsset(tokenId, index, assetId); - } - - function rejectAllAssets( - uint256 tokenId, - uint256 maxRejections - ) external virtual { - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - - uint256 len = _pendingAssets[tokenId].length; - if (len > maxRejections) revert("Unexpected number of assets"); - - _beforeRejectAllAssets(tokenId); - for (uint256 i; i < len; ) { - uint64 assetId = _pendingAssets[tokenId][i]; - delete _assetReplacements[tokenId][assetId]; - unchecked { - ++i; - } - } - delete (_pendingAssets[tokenId]); - - emit AssetRejected(tokenId, uint64(0)); - _afterRejectAllAssets(tokenId); - } - - function setPriority( - uint256 tokenId, - uint64[] memory priorities - ) external virtual { - uint256 length = priorities.length; - require( - length == _activeAssets[tokenId].length, - "MultiAsset: Bad priority list length" - ); - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - - _beforeSetPriority(tokenId, priorities); - _activeAssetPriorities[tokenId] = priorities; - - emit AssetPrioritySet(tokenId); - _afterSetPriority(tokenId, priorities); - } - - function getActiveAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _activeAssets[tokenId]; - } - - function getPendingAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _pendingAssets[tokenId]; - } - - function getActiveAssetPriorities( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _activeAssetPriorities[tokenId]; - } - - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) public view virtual returns (uint64) { - return _assetReplacements[tokenId][newAssetId]; - } - - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) public view virtual returns (string memory) { - if (!_tokenAssets[tokenId][assetId]) - revert("MultiAsset: Token does not have asset"); - return _assets[assetId]; - } - - function tokenURI( - uint256 tokenId - ) public view virtual returns (string memory) { - return ""; - } - - // To be implemented with custom guards - - function _addAssetEntry(uint64 id, string memory metadataURI) internal { - require(id != uint64(0), "RMRK: Write to zero"); - require(bytes(_assets[id]).length == 0, "RMRK: asset already exists"); - - _beforeAddAsset(id, metadataURI); - _assets[id] = metadataURI; - - emit AssetSet(id); - _afterAddAsset(id, metadataURI); - } - - function _addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal { - require( - !_tokenAssets[tokenId][assetId], - "MultiAsset: Asset already exists on token" - ); - - require( - bytes(_assets[assetId]).length != 0, - "MultiAsset: Asset not found in storage" - ); - - require( - _pendingAssets[tokenId].length < 128, - "MultiAsset: Max pending assets reached" - ); - - _beforeAddAssetToToken(tokenId, assetId, replacesAssetWithId); - _tokenAssets[tokenId][assetId] = true; - _pendingAssets[tokenId].push(assetId); - - if (replacesAssetWithId != uint64(0)) { - _assetReplacements[tokenId][assetId] = replacesAssetWithId; - } - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - emit AssetAddedToTokens(tokenIds, assetId, replacesAssetWithId); - _afterAddAssetToToken(tokenId, assetId, replacesAssetWithId); - } - - // HOOKS - - function _beforeAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - function _afterAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - function _beforeAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - function _afterAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - function _beforeAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _afterAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _beforeRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _afterRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _beforeRejectAllAssets(uint256 tokenId) internal virtual {} - - function _afterRejectAllAssets(uint256 tokenId) internal virtual {} - - function _beforeSetPriority( - uint256 tokenId, - uint64[] memory priorities - ) internal virtual {} - - function _afterSetPriority( - uint256 tokenId, - uint64[] memory priorities - ) internal virtual {} -} diff --git a/assets/eip-5773/contracts/library/MultiAssetLib.sol b/assets/eip-5773/contracts/library/MultiAssetLib.sol deleted file mode 100644 index 24a9cc0..0000000 --- a/assets/eip-5773/contracts/library/MultiAssetLib.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -library MultiAssetLib { - function indexOf( - uint64[] memory A, - uint64 a - ) internal pure returns (uint256, bool) { - uint256 length = A.length; - for (uint256 i; i < length; ) { - if (A[i] == a) { - return (i, true); - } - unchecked { - ++i; - } - } - return (0, false); - } - - //For reasource storage array - function removeItemByIndex(uint64[] storage array, uint256 index) internal { - //Check to see if this is already gated by require in all calls - require(index < array.length); - array[index] = array[array.length - 1]; - array.pop(); - } -} diff --git a/assets/eip-5773/contracts/mocks/ERC721ReceiverMock.sol b/assets/eip-5773/contracts/mocks/ERC721ReceiverMock.sol deleted file mode 100644 index 1f0665b..0000000 --- a/assets/eip-5773/contracts/mocks/ERC721ReceiverMock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract ERC721ReceiverMock { - bytes4 constant ERC721_RECEIVED = 0x150b7a02; - - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) public returns (bytes4) { - return ERC721_RECEIVED; - } -} diff --git a/assets/eip-5773/contracts/mocks/MultiAssetTokenMock.sol b/assets/eip-5773/contracts/mocks/MultiAssetTokenMock.sol deleted file mode 100644 index 0ceafe7..0000000 --- a/assets/eip-5773/contracts/mocks/MultiAssetTokenMock.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -import "../MultiAssetToken.sol"; - -contract MultiAssetTokenMock is MultiAssetToken { - address private _issuer; - - constructor( - string memory name, - string memory symbol - ) MultiAssetToken(name, symbol) { - _setIssuer(_msgSender()); - } - - modifier onlyIssuer() { - require(_msgSender() == _issuer, "RMRK: Only issuer"); - _; - } - - function setIssuer(address issuer) external onlyIssuer { - _setIssuer(issuer); - } - - function getIssuer() external view returns (address) { - return _issuer; - } - - function mint(address to, uint256 tokenId) external onlyIssuer { - _mint(to, tokenId); - } - - function transfer(address to, uint256 tokenId) external { - _transfer(msg.sender, to, tokenId); - } - - function burn(uint256 tokenId) external { - _burn(tokenId); - } - - function addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 overwrites - ) external onlyIssuer { - _addAssetToToken(tokenId, assetId, overwrites); - } - - function addAssetEntry( - uint64 id, - string memory metadataURI - ) external onlyIssuer { - _addAssetEntry(id, metadataURI); - } - - function _setIssuer(address issuer) private { - _issuer = issuer; - } -} diff --git a/assets/eip-5773/contracts/mocks/NonReceiverMock.sol b/assets/eip-5773/contracts/mocks/NonReceiverMock.sol deleted file mode 100644 index e9d2d6e..0000000 --- a/assets/eip-5773/contracts/mocks/NonReceiverMock.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract NonReceiverMock { - function dummy() external {} -} diff --git a/assets/eip-5773/contracts/utils/MultiAssetRenderUtils.sol b/assets/eip-5773/contracts/utils/MultiAssetRenderUtils.sol deleted file mode 100644 index 4a1ccbc..0000000 --- a/assets/eip-5773/contracts/utils/MultiAssetRenderUtils.sol +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -import "../IERC5773.sol"; - -pragma solidity ^0.8.15; - -/** - * @dev Extra utility functions for composing RMRK assets. - */ - -contract MultiAssetRenderUtils { - uint64 private constant _LOWEST_POSSIBLE_PRIORITY = 2 ** 16 - 1; - - struct ActiveAsset { - uint64 id; - uint64 priority; - string metadata; - } - - struct PendingAsset { - uint64 id; - uint128 acceptRejectIndex; - uint64 overwritesAssetWithId; - string metadata; - } - - function getActiveAssets( - address target, - uint256 tokenId - ) public view virtual returns (ActiveAsset[] memory) { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint64[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert("Token has no assets"); - } - - ActiveAsset[] memory activeAssets = new ActiveAsset[](len); - string memory metadata; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - activeAssets[i] = ActiveAsset({ - id: assets[i], - priority: priorities[i], - metadata: metadata - }); - unchecked { - ++i; - } - } - return activeAssets; - } - - function getPendingAssets( - address target, - uint256 tokenId - ) public view virtual returns (PendingAsset[] memory) { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getPendingAssets(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert("Token has no assets"); - } - - PendingAsset[] memory pendingAssets = new PendingAsset[](len); - string memory metadata; - uint64 overwritesAssetWithId; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - overwritesAssetWithId = target_.getAssetReplacements( - tokenId, - assets[i] - ); - pendingAssets[i] = PendingAsset({ - id: assets[i], - acceptRejectIndex: uint128(i), - overwritesAssetWithId: overwritesAssetWithId, - metadata: metadata - }); - unchecked { - ++i; - } - } - return pendingAssets; - } - - /** - * @notice Returns asset metadata strings for the given ids - * - * Requirements: - * - * - `assetIds` must exist. - */ - function getAssetsById( - address target, - uint256 tokenId, - uint64[] calldata assetIds - ) public view virtual returns (string[] memory) { - IERC5773 target_ = IERC5773(target); - uint256 len = assetIds.length; - string[] memory assets = new string[](len); - for (uint256 i; i < len; ) { - assets[i] = target_.getAssetMetadata(tokenId, assetIds[i]); - unchecked { - ++i; - } - } - return assets; - } - - /** - * @notice Returns the asset metadata with the highest priority for the given token - */ - function getTopAssetMetaForToken( - address target, - uint256 tokenId - ) external view returns (string memory) { - IERC5773 target_ = IERC5773(target); - uint64[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint256 len = priorities.length; - if (len == 0) { - revert("Token has no assets"); - } - - uint64 maxPriority = _LOWEST_POSSIBLE_PRIORITY; - uint64 maxPriorityAsset; - for (uint64 i; i < len; ) { - uint64 currentPrio = priorities[i]; - if (currentPrio < maxPriority) { - maxPriority = currentPrio; - maxPriorityAsset = assets[i]; - } - unchecked { - ++i; - } - } - return target_.getAssetMetadata(tokenId, maxPriorityAsset); - } -} diff --git a/assets/eip-5773/hardhat.config.ts b/assets/eip-5773/hardhat.config.ts deleted file mode 100644 index 1c14d97..0000000 --- a/assets/eip-5773/hardhat.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@nomiclabs/hardhat-etherscan"; -import "@typechain/hardhat"; -import "hardhat-contract-sizer"; -import "hardhat-gas-reporter"; -import "solidity-coverage"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.15", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-5773/package.json b/assets/eip-5773/package.json deleted file mode 100644 index e6f2346..0000000 --- a/assets/eip-5773/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "eip-5773", - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/test-helpers": "^0.5.15", - "@primitivefi/hardhat-dodoc": "^0.2.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.3", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "chai": "^4.3.6", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.0", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "hardhat-contract-sizer": "^2.6.1", - "hardhat-gas-reporter": "^1.0.8", - "prettier": "2.7.1", - "prettier-plugin-solidity": "^1.0.0-beta.20", - "solc": "^0.8.9", - "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "walk-sync": "^3.0.0" - } -} diff --git a/assets/eip-5773/test/multiasset.ts b/assets/eip-5773/test/multiasset.ts deleted file mode 100644 index e623e4e..0000000 --- a/assets/eip-5773/test/multiasset.ts +++ /dev/null @@ -1,761 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { - ERC721ReceiverMock, - MultiAssetReceiverMock, - MultiAssetTokenMock, - NonReceiverMock, - MultiAssetRenderUtils, -} from "../typechain-types"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { BigNumber } from "ethers"; - -describe("MultiAsset", async () => { - let token: MultiAssetTokenMock; - let renderUtils: MultiAssetRenderUtils; - let nonReceiver: NonReceiverMock; - let receiver721: ERC721ReceiverMock; - - let owner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - const name = "RmrkTest"; - const symbol = "RMRKTST"; - - const metaURIDefault = "metaURI"; - - beforeEach(async () => { - const [signersOwner, ...signersAddr] = await ethers.getSigners(); - owner = signersOwner; - addrs = signersAddr; - - const multiassetFactory = await ethers.getContractFactory( - "MultiAssetTokenMock" - ); - token = await multiassetFactory.deploy(name, symbol); - await token.deployed(); - - const renderFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - renderUtils = await renderFactory.deploy(); - await renderUtils.deployed(); - }); - - describe("Init", async function () { - it("Name", async function () { - expect(await token.name()).to.equal(name); - }); - - it("Symbol", async function () { - expect(await token.symbol()).to.equal(symbol); - }); - }); - - describe("ERC165 check", async function () { - it("can support IERC165", async function () { - expect(await token.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("can support IERC721", async function () { - expect(await token.supportsInterface("0x80ac58cd")).to.equal(true); - }); - - it("can support IERC5773", async function () { - expect(await token.supportsInterface("0x06b4329a")).to.equal(true); - }); - - it("cannot support other interfaceId", async function () { - expect(await token.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("Check OnReceived ERC721 and Multiasset", async function () { - it("Revert on transfer to non onERC721/onMultiasset implementer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const NonReceiver = await ethers.getContractFactory("NonReceiverMock"); - nonReceiver = await NonReceiver.deploy(); - await nonReceiver.deployed(); - - await expect( - token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - nonReceiver.address, - 1 - ) - ).to.be.revertedWith( - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - }); - - it("onERC721Received callback on transfer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const ERC721Receiver = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - receiver721 = await ERC721Receiver.deploy(); - await receiver721.deployed(); - - await token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - receiver721.address, - 1 - ); - expect(await token.ownerOf(1)).to.equal(receiver721.address); - }); - }); - - describe("Asset storage", async function () { - it("can add asset", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - }); - - it("cannot get non existing asset", async function () { - const tokenId = 1; - const resId = 10; - await token.mint(owner.address, tokenId); - await expect(token.getAssetMetadata(tokenId, resId)).to.be.revertedWith( - "MultiAsset: Token does not have asset" - ); - }); - - it("cannot add asset entry if not issuer", async function () { - const id = 10; - await expect( - token.connect(addrs[1]).addAssetEntry(id, metaURIDefault) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("can set and get issuer", async function () { - const newIssuerAddr = addrs[1].address; - expect(await token.getIssuer()).to.equal(owner.address); - - await token.setIssuer(newIssuerAddr); - expect(await token.getIssuer()).to.equal(newIssuerAddr); - }); - - it("cannot set issuer if not issuer", async function () { - const newIssuer = addrs[1]; - await expect( - token.connect(newIssuer).setIssuer(newIssuer.address) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("cannot overwrite asset", async function () { - const id = 10; - - await token.addAssetEntry(id, metaURIDefault); - await expect(token.addAssetEntry(id, metaURIDefault)).to.be.revertedWith( - "RMRK: asset already exists" - ); - }); - - it("cannot add asset with id 0", async function () { - const id = ethers.utils.hexZeroPad("0x0", 8); - - await expect(token.addAssetEntry(id, metaURIDefault)).to.be.revertedWith( - "RMRK: Write to zero" - ); - }); - - it("cannot add same asset twice", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - - await expect(token.addAssetEntry(id, metaURIDefault)).to.be.revertedWith( - "RMRK: asset already exists" - ); - }); - }); - - describe("Adding assets", async function () { - it("can add asset to token", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId2, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - - const pendingIds = await token.getPendingAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, pendingIds) - ).to.be.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot add non existing asset to token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.be.revertedWith( - "MultiAsset: Asset not found in storage" - ); - }); - - it("can add asset to non existing token and it is pending when minted", async function () { - const resId = 1; - const tokenId = 1; - await addAssets([resId]); - - await token.addAssetToToken(tokenId, resId, 0); - await token.mint(owner.address, tokenId); - expect(await token.getPendingAssets(tokenId)).to.eql([ - ethers.BigNumber.from(resId), - ]); - }); - - it("cannot add asset twice to the same token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.addAssetToToken(tokenId, ethers.BigNumber.from(resId), 0) - ).to.be.revertedWith("MultiAsset: Asset already exists on token"); - }); - - it("cannot add too many assets to the same token", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - for (let i = 1; i <= 128; i++) { - await addAssets([i]); - await token.addAssetToToken(tokenId, i, 0); - } - - // Now it's full, next should fail - const resId = 129; - await addAssets([resId]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.be.revertedWith( - "MultiAsset: Max pending assets reached" - ); - }); - - it("can add same asset to 2 different tokens", async function () { - const resId = 1; - const tokenId1 = 1; - const tokenId2 = 2; - - await token.mint(owner.address, tokenId1); - await token.mint(owner.address, tokenId2); - await addAssets([resId]); - await token.addAssetToToken(tokenId1, resId, 0); - await token.addAssetToToken(tokenId2, resId, 0); - }); - }); - - describe("Accepting assets", async function () { - it("can accept asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept multiple assets", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 1, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot accept asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - }); - - it("cannot accept asset if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.connect(addrs[1]).acceptAsset(tokenId, 0, resId) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - }); - - it("cannot accept non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect(token.acceptAsset(tokenId, 0, 1)).to.be.revertedWith( - "MultiAsset: index out of bounds" - ); - }); - }); - - describe("Overwriting assets", async function () { - it("can add asset to token overwritting an existing one", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Add new asset to overwrite the first, and accept - const activeAssets = await token.getActiveAssets(tokenId); - await expect(token.addAssetToToken(tokenId, resId2, activeAssets[0])) - .to.emit(token, "AssetAddedToTokens") - .withArgs([tokenId], resId2, resId); - const pendingAssets = await token.getPendingAssets(tokenId); - - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(activeAssets[0]); - await expect(token.acceptAsset(tokenId, 0, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - // Overwrite should be gone - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(ethers.BigNumber.from(0)); - }); - - it("can overwrite non existing asset to token, it could have been deleted", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken( - tokenId, - resId, - ethers.utils.hexZeroPad("0x1", 8) - ); - await token.acceptAsset(tokenId, 0, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - }); - - it("can overwrite an existing asset after 3 have been added and 1 accepted", async function () { - const resId = 1; - const resId2 = 2; - const resId3 = 3; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2, resId3]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId2, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId3, resId2)) - .to.emit(token, "AssetAddedToTokens") - .withArgs([tokenId], resId3, resId2); - - const pendingIds = await token.getPendingAssets(tokenId); - - expect( - await renderUtils.getAssetsById(token.address, tokenId, pendingIds) - ).to.be.eql([metaURIDefault, metaURIDefault, metaURIDefault]); - - await expect(token.acceptAsset(tokenId, 1, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, 0); - - await expect(token.acceptAsset(tokenId, 1, resId3)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId3, 2); - }); - }); - - describe("Rejecting assets", async function () { - it("can reject asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject all assets if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject asset and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject it - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAsset(tokenId, 0, resId2); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all assets and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject all - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAllAssets(tokenId, 1); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all pending assets at max capacity", async function () { - const tokenId = 1; - const resArr = []; - - for (let i = 1; i < 128; i++) { - resArr.push(i); - } - - await token.mint(owner.address, tokenId); - await addAssets(resArr); - - for (let i = 1; i < 128; i++) { - await token.addAssetToToken(tokenId, i, 1); - } - await token.rejectAllAssets(tokenId, 128); - - expect(await token.getAssetReplacements(1, 2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("cannot reject asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.rejectAsset(tokenId, 0, resId); - }); - - it("cannot reject asset nor reject all if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(addrs[1]).rejectAsset(tokenId, 0, resId) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - await expect( - token.connect(addrs[1]).rejectAllAssets(tokenId, 1) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - }); - - it("cannot reject non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect(token.rejectAsset(tokenId, 0, 1)).to.be.revertedWith( - "MultiAsset: index out of bounds" - ); - }); - }); - - describe("Priorities", async function () { - it("can set and get priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([BigNumber.from(0), BigNumber.from(1)]); - await expect(token.setPriority(tokenId, [2, 1])) - .to.emit(token, "AssetPrioritySet") - .withArgs(tokenId); - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([BigNumber.from(2), BigNumber.from(1)]); - }); - - it("cannot set priorities for non owned token", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [2, 1]) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - }); - - it("cannot set different number of priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [1]) - ).to.be.revertedWith("MultiAsset: Bad priority list length"); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [2, 1, 3]) - ).to.be.revertedWith("MultiAsset: Bad priority list length"); - }); - - it("cannot set priorities for non existing token", async function () { - const tokenId = 1; - await expect( - token.connect(addrs[1]).setPriority(tokenId, []) - ).to.be.revertedWith("MultiAsset: approved query for nonexistent token"); - }); - }); - - describe("Approval Cleaning", async function () { - it("cleans token and assets approvals on transfer", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const newOwner = addrs[2]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner).transfer(newOwner.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql( - ethers.constants.AddressZero - ); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - ethers.constants.AddressZero - ); - }); - - it("cleans token and assets approvals on burn", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner).burn(tokenId); - - await expect(token.getApproved(tokenId)).to.be.revertedWith( - "MultiAsset: approved query for nonexistent token" - ); - await expect(token.getApprovedForAssets(tokenId)).to.be.revertedWith( - "MultiAsset: approved query for nonexistent token" - ); - }); - }); - - async function mintSampleToken(): Promise<{ - tokenOwner: SignerWithAddress; - tokenId: number; - }> { - const tokenOwner = owner; - const tokenId = 1; - await token.mint(tokenOwner.address, tokenId); - - return { tokenOwner, tokenId }; - } - - async function addAssets(ids: number[]): Promise { - ids.forEach(async (resId) => { - await token.addAssetEntry(resId, metaURIDefault); - }); - } - - async function addAssetsToToken(tokenId: number): Promise { - const resId = 1; - const resId2 = 2; - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await token.acceptAsset(tokenId, 0, resId); - await token.acceptAsset(tokenId, 0, resId2); - } - - async function checkAcceptFromAddress( - accepter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect(token.connect(accepter).acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - } - - async function checkRejectFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(rejecter).rejectAsset(tokenId, 0, resId) - ).to.emit(token, "AssetRejected"); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } - - async function checkRejectAllFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - const resId2 = 2; - - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - - await expect(token.connect(rejecter).rejectAllAssets(tokenId, 2)).to.emit( - token, - "AssetRejected" - ); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } -}); diff --git a/assets/eip-5773/test/renderUtils.ts b/assets/eip-5773/test/renderUtils.ts deleted file mode 100644 index 78bc785..0000000 --- a/assets/eip-5773/test/renderUtils.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { MultiAssetTokenMock, MultiAssetRenderUtils } from "../typechain-types"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -async function assetsFixture() { - const multiassetFactory = await ethers.getContractFactory( - "MultiAssetTokenMock" - ); - const renderUtilsFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - - const multiasset = await multiassetFactory.deploy("Chunky", "CHNK"); - await multiasset.deployed(); - - const renderUtils = await renderUtilsFactory.deploy(); - await renderUtils.deployed(); - - return { multiasset, renderUtils }; -} - -describe("Render Utils", async function () { - let owner: SignerWithAddress; - let multiasset: MultiAssetTokenMock; - let renderUtils: MultiAssetRenderUtils; - let tokenId: number; - - const resId = bn(1); - const resId2 = bn(2); - const resId3 = bn(3); - const resId4 = bn(4); - - before(async function () { - ({ multiasset, renderUtils } = await loadFixture(assetsFixture)); - - const signers = await ethers.getSigners(); - owner = signers[0]; - - tokenId = 1; - await multiasset.mint(owner.address, tokenId); - await multiasset.addAssetEntry(resId, "ipfs://res1.jpg"); - await multiasset.addAssetEntry(resId2, "ipfs://res2.jpg"); - await multiasset.addAssetEntry(resId3, "ipfs://res3.jpg"); - await multiasset.addAssetEntry(resId4, "ipfs://res4.jpg"); - await multiasset.addAssetToToken(tokenId, resId, 0); - await multiasset.addAssetToToken(tokenId, resId2, 0); - await multiasset.addAssetToToken(tokenId, resId3, resId); - await multiasset.addAssetToToken(tokenId, resId4, 0); - - await multiasset.acceptAsset(tokenId, 0, resId); - await multiasset.acceptAsset(tokenId, 1, resId2); - await multiasset.setPriority(tokenId, [10, 5]); - }); - - describe("Render Utils MultiAsset", async function () { - it("can get active assets", async function () { - expect( - await renderUtils.getActiveAssets(multiasset.address, tokenId) - ).to.eql([ - [resId, BigNumber.from(10), "ipfs://res1.jpg"], - [resId2, BigNumber.from(5), "ipfs://res2.jpg"], - ]); - }); - it("can get pending assets", async function () { - expect( - await renderUtils.getPendingAssets(multiasset.address, tokenId) - ).to.eql([ - [resId4, bn(0), bn(0), "ipfs://res4.jpg"], - [resId3, bn(1), resId, "ipfs://res3.jpg"], - ]); - }); - - it("can get top asset by priority", async function () { - expect( - await renderUtils.getTopAssetMetaForToken(multiasset.address, tokenId) - ).to.eql("ipfs://res2.jpg"); - }); - - it("cannot get top asset if token has no assets", async function () { - const otherTokenId = 2; - await multiasset.mint(owner.address, otherTokenId); - await expect( - renderUtils.getTopAssetMetaForToken(multiasset.address, otherTokenId) - ).to.be.revertedWith("Token has no assets"); - }); - }); -}); diff --git a/assets/eip-5827/ERC5827.sol b/assets/eip-5827/ERC5827.sol deleted file mode 100644 index 1d70d48..0000000 --- a/assets/eip-5827/ERC5827.sol +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.17; - -import "openzeppelin-contracts/token/ERC20/ERC20.sol"; -import "./IERC5827.sol"; - -contract ERC5827 is ERC20, IERC5827 { - struct RenewableAllowance { - uint256 amount; - uint192 recoveryRate; - uint64 lastUpdated; - } - - // owner => spender => renewableAllowance - mapping(address => mapping(address => RenewableAllowance)) - private rAllowance; - - constructor( - string memory name_, - string memory symbol_ - ) ERC20(name_, symbol_) {} - - function approve( - address _spender, - uint256 _value - ) public override(ERC20, IERC5827) returns (bool success) { - address owner = _msgSender(); - _approve(owner, _spender, _value, 0); - return true; - } - - function approveRenewable( - address _spender, - uint256 _value, - uint256 _recoveryRate - ) public override returns (bool success) { - address owner = _msgSender(); - _approve(owner, _spender, _value, _recoveryRate); - return true; - } - - function _approve( - address _owner, - address _spender, - uint256 _value, - uint256 _recoveryRate - ) internal virtual { - require( - _recoveryRate <= _value, - "recoveryRate must be less than or equal to value" - ); - - rAllowance[_owner][_spender] = RenewableAllowance({ - amount: _value, - recoveryRate: uint192(_recoveryRate), - lastUpdated: uint64(block.timestamp) - }); - - _approve(_owner, _spender, _value); - emit RenewableApproval(_owner, _spender, _value, _recoveryRate); - } - - /// @notice fetch amounts spendable by _spender - /// @return remaining allowance at the current point in time - function allowance( - address _owner, - address _spender - ) public view override(ERC20, IERC5827) returns (uint256 remaining) { - return _remainingAllowance(_owner, _spender); - } - - /// @dev returns the sum of two uint256 values, saturating at 2**256 - 1 - function saturatingAdd( - uint256 a, - uint256 b - ) internal pure returns (uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return type(uint256).max; - return c; - } - } - - function _remainingAllowance( - address _owner, - address _spender - ) private view returns (uint256) { - RenewableAllowance memory a = rAllowance[_owner][_spender]; - uint256 remaining = super.allowance(_owner, _spender); - - uint256 recovered = uint256(a.recoveryRate) * - uint64(block.timestamp - a.lastUpdated); - uint256 remainingAllowance = saturatingAdd(remaining, recovered); - return remainingAllowance > a.amount ? a.amount : remainingAllowance; - } - - /// @notice fetch approved max amount and recovery rate - /// @return amount initial and maximum allowance given to spender - /// @return recoveryRate recovery amount per second - function renewableAllowance( - address _owner, - address _spender - ) public view returns (uint256 amount, uint256 recoveryRate) { - RenewableAllowance memory a = rAllowance[_owner][_spender]; - return (a.amount, uint256(a.recoveryRate)); - } - - /// @notice transfers base token with renewable allowance logic applied - /// @param from owner of base token - /// @param to recipient of base token - /// @param amount amount to transfer - function transferFrom( - address from, - address to, - uint256 amount - ) public override(ERC20, IERC5827) returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual override { - (uint256 maxAllowance, ) = renewableAllowance(owner, spender); - if (maxAllowance != type(uint256).max) { - uint256 currentAllowance = _remainingAllowance(owner, spender); - if (currentAllowance < amount) { - revert InsufficientRenewableAllowance({ - available: currentAllowance - }); - } - - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - rAllowance[owner][spender].lastUpdated = uint64(block.timestamp); - } - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return interfaceId == type(IERC5827).interfaceId; - } -} diff --git a/assets/eip-5827/IERC5827.sol b/assets/eip-5827/IERC5827.sol deleted file mode 100644 index 2f45588..0000000 --- a/assets/eip-5827/IERC5827.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import "openzeppelin-contracts/interfaces/IERC20.sol"; -import "openzeppelin-contracts/interfaces/IERC165.sol"; - -/// @title Interface for IERC5827 contracts -/// @notice Please see https://eips.ethereum.org/EIPS/eip-5827 for more details on the goals of this interface -/// @author Zac (zlace0x), zhongfu (zhongfu), Edison (edison0xyz) -interface IERC5827 is IERC20, IERC165 { - /// Note: the ERC-165 identifier for this interface is 0x93cd7af6. - /// 0x93cd7af6 === - /// bytes4(keccak256('approveRenewable(address,uint256,uint256)')) ^ - /// bytes4(keccak256('renewableAllowance(address,address)')) ^ - /// bytes4(keccak256('approve(address,uint256)') ^ - /// bytes4(keccak256('transferFrom(address,address,uint256)') ^ - /// bytes4(keccak256('allowance(address,address)') ^ - - /// @dev Thrown when there available allowance is lesser than transfer amount - /// @param available Allowance available, 0 if unset - error InsufficientRenewableAllowance(uint256 available); - - /// @notice Emitted when a new renewable allowance is set. - /// @param _owner owner of token - /// @param _spender allowed spender of token - /// @param _value initial and maximum allowance given to spender - /// @param _recoveryRate recovery amount per second - event RenewableApproval( - address indexed _owner, - address indexed _spender, - uint256 _value, - uint256 _recoveryRate - ); - - /// @notice Grants an allowance of `_value` to `_spender` initially, which recovers over time based on `_recoveryRate` up to a limit of `_value`. - /// SHOULD throw when `_recoveryRate` is larger than `_value`. - /// MUST emit `RenewableApproval` event. - /// @param _spender allowed spender of token - /// @param _value initial and maximum allowance given to spender - /// @param _recoveryRate recovery amount per second - function approveRenewable( - address _spender, - uint256 _value, - uint256 _recoveryRate - ) external returns (bool success); - - /// @notice Returns approved max amount and recovery rate. - /// @return amount initial and maximum allowance given to spender - /// @return recoveryRate recovery amount per second - function renewableAllowance( - address _owner, - address _spender - ) external view returns (uint256 amount, uint256 recoveryRate); - - /// Overridden EIP-20 functions - - /// @notice Grants a (non-increasing) allowance of _value to _spender. - /// MUST clear set _recoveryRate to 0 on the corresponding renewable allowance, if any. - /// @param _spender allowed spender of token - /// @param _value allowance given to spender - function approve( - address _spender, - uint256 _value - ) external returns (bool success); - - /// @notice Moves `amount` tokens from `from` to `to` using the - /// allowance mechanism. `amount` is then deducted from the caller's - /// allowance factoring in recovery rate logic. - /// SHOULD throw when there is insufficient allowance - /// @param from token owner address - /// @param to token recipient - /// @param amount amount of token to transfer - /// @return success True if the function is successful, false if otherwise - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool success); - - /// @notice Returns amounts spendable by `_spender`. - /// @param _owner Address of the owner - /// @param _spender spender of token - /// @return remaining allowance at the current point in time - function allowance( - address _owner, - address _spender - ) external view returns (uint256 remaining); -} diff --git a/assets/eip-5851/contracts/ERC5851Issuer.sol b/assets/eip-5851/contracts/ERC5851Issuer.sol deleted file mode 100644 index f6e91de..0000000 --- a/assets/eip-5851/contracts/ERC5851Issuer.sol +++ /dev/null @@ -1,46 +0,0 @@ - -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./interfaces/IERC5851.sol"; - - -abstract contract ERC5851Issuer is IERC5851{ - mapping(uint256 => IERC5851.Claim[]) private _claimMetadata; - mapping(address => mapping(uint256 => bool)) private _SBTVerified; - address public admin; - - constructor() { - admin = msg.sender; - - } - - function ifVerified(address claimmer, uint256 SBTID) public override view returns (bool){ - return(_SBTVerified[claimmer][SBTID]); - } - - function standardClaim(uint256 SBTID) public override view returns (Claim[] memory){ - return(_claimMetadata[SBTID]); - } - - function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) public override returns (bool){ - require(msg.sender == admin); - _claimMetadata[SBTID] = _claims; - emit StandardChanged(SBTID, _claims); - return(true); - } - - function certify(address claimer, uint256 SBTID) public override returns (bool){ - require(msg.sender == admin); - _SBTVerified[claimer][SBTID] = true; - emit Certified(claimer, SBTID); - return(true); - } - - function revoke(address claimer, uint256 SBTID) external override returns (bool){ - require(msg.sender == admin); - _SBTVerified[claimer][SBTID] = false; - emit Revoked(claimer, SBTID); - return(true); - } - -} diff --git a/assets/eip-5851/contracts/ERC5851Verifier.sol b/assets/eip-5851/contracts/ERC5851Verifier.sol deleted file mode 100644 index 75c6633..0000000 --- a/assets/eip-5851/contracts/ERC5851Verifier.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./interfaces/IERC5851.sol"; - -abstract contract ERC5851Verifier is IERC5851 { - address private _issuer; - - constructor(address issuer) { - _issuer = issuer; - } - - modifier KYCApproved(address claimer, uint256 SBTID) { - IERC5851(_issuer).ifVerified(claimer, SBTID); - _; - } - -} diff --git a/assets/eip-5851/contracts/interfaces/IERC5851.sol b/assets/eip-5851/contracts/interfaces/IERC5851.sol deleted file mode 100644 index a045e9c..0000000 --- a/assets/eip-5851/contracts/interfaces/IERC5851.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5851{ - - /** Metadata - * - * @param title defines the name of the claim field - * @param kind is the nature of the data (bool,string,address,bytes,..) - * @param description additional information about claim details. - */ - struct Metadata { - string title; - string kind; - string description; - } - - /** Values - * - * @dev Values here can be read and wrote by smartcontract and front-end, cited from [EIP-3475](./eip-3475.md) - */ - struct Values { - string stringValue; - uint uintValue; - address addressValue; - bool boolValue; - } - - /** Claim - * - * Claims structure consist of the conditions and value that holder claims to associate and verifier has to validate them. - * @notice the below given parameters are for reference purposes only, developers can optimize the fields that are needed to be represented on-chain by using schemes like TLV, encoding into base64 etc. - * @dev structure that DeFines the parameters for specific claims of the SBT certificate - * @notice this structure is used for the verification process, it contains the metadata, logic and expectation - * @logic given here MUST be one of ("⊄", "⊂", "<", "<=", "==", "!=", ">=",">") - */ - struct Claim { - Metadata metadata; - string logic; - Values expectation; - } - - //Verifier - /// @notice getter function to validate if the address `claimer` is the holder of the claim Defined by the tokenId `SBTID` - /// @dev it MUST be Defining the logic to fetch the result of the ZK verification (either from). - /// @dev logic given here MUST be one of ("⊄", "⊂", "<", "<=", "==", "!=", ">=", ">") - /// @param claimer is the EOA address that wants to validate the SBT issued to it by the KYC. - /// @param SBTID is the Id of the SBT that user is the claimer. - /// @return true if the assertion is valid, else false - /** - example ifVerified(0xfoo, 1) => true will mean that 0xfoo is the holder of the SBT identity token DeFined by tokenId of the given collection. - */ - function ifVerified(address claimer, uint256 SBTID) external view returns (bool); - - //Issuer - /// @notice getter function to fetch the on-chain identification logic for the given identity holder. - /// @dev it MUST not be defined for address(0). - /// @param SBTID is the Id of the SBT that the user is the claimer. - /// @return the struct array of all the descriptions of condition metadata that is defined by the administrator for the given KYC provider. - /** - ex: standardClaim(1) --> { - { "title":"age", - "kind": "uint", - "description": "age of the person based on the birth date on the legal document", - }, - "logic": ">=", - "value":"18" - } - Defines the condition encoded for the identity index 1, DeFining the identity condition that holder must be equal or more than 18 years old. - **/ - function standardClaim(uint256 SBTID) external view returns (Claim[] memory); - - /// @notice function for setting the claim requirement logic (defined by Claims metadata) details for the given identity token defined by SBTID. - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims. - /// @param `claims` is the struct array of all the descriptions of condition metadata that is defined by the administrator. check metadata section for more information. - /** - example: changeStandardClaim(1, { "title":"age", - "kind": "uint", - "description": "age of the person based on the birth date on the legal document", - }, - "logic": ">=", - "value":"18" - }); - will correspond to the functionality that admin needs to adjust the standard claim for the identification SBT with tokenId = 1, based on the conditions described in the Claims array struct details. - **/ - function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) external returns (bool); - - /// @notice function which uses the ZKProof protocol to validate the identity based on the given - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which admin wants to define the Claims. - /// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID. - /** - example: certify(0xA....., 10) means that admin assigns the DID badge with id 10 to the address defined by the `0xA....` wallet. - */ - function certify(address claimer, uint256 SBTID) external returns (bool); - - /// @notice function which uses the ZKProof protocol to validate the identity based on the given - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims. - /// @param certifying is the address that needs to be proven as the owner of the SBT defined by the tokenID. - // eg: revoke(0xfoo,1): means that KYC admin revokes the SBT certificate number 1 for the address '0xfoo'. - function revoke(address certifying, uint256 SBTID) external returns (bool); - - -// Events - /** - * standardChanged - * @notice standardChanged MUST be triggered when claims are changed by the admin. - * @dev standardChanged MUST also be triggered for the creation of a new SBTID. - e.g : emit StandardChanged(1, Claims(Metadata('age', 'uint', 'age of the person based on the birth date on the legal document'), ">=", "18"); - is emitted when the Claim condition is changed which allows the certificate holder to call the functions with the modifier, claims that the holder must be equal or more than 18 years old. - */ - event StandardChanged(uint256 SBTID, Claim[] _claims); - - /** - * certified - * @notice certified MUST be triggered when the SBT certificate is given to the certifying address. - * eg: Certified(0xfoo,2); means that wallet holder address 0xfoo is certified to hold a certificate issued with id 2, and thus can satisfy all the conditions defined by the required interface. - */ - event Certified(address claimer, uint256 SBTID); - - /** - * revoked - * @notice revoked MUST be triggered when the SBT certificate is revoked. - * eg: Revoked( 0xfoo,1); means that entity user 0xfoo has been revoked to all the function access defined by the SBT ID 1. - */ - event Revoked(address claimer, uint256 SBTID); -} diff --git a/assets/eip-5851/contracts/test.sol b/assets/eip-5851/contracts/test.sol deleted file mode 100644 index a299dbb..0000000 --- a/assets/eip-5851/contracts/test.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./ERC5851Verifier.sol"; - -abstract contract Token is ERC5851Verifier { - uint public test; - uint public SBTID; - function mint(address to, uint256 amount) public KYCApproved(to, SBTID){ - _mint(to, amount); - } - - function _mint(address account, uint256 amount) internal { - require(account != address(0), "ERC20: mint to the zero address"); - test = amount; - } - -} diff --git a/assets/eip-5851/script/offchainOperations.js b/assets/eip-5851/script/offchainOperations.js deleted file mode 100644 index e2636d3..0000000 --- a/assets/eip-5851/script/offchainOperations.js +++ /dev/null @@ -1,69 +0,0 @@ -// CC0 license. -// taken from (credits): https://soliditydeveloper.com/merkle-tree -const keccak256 = require("keccak256"); -const { MerkleTree } = require("merkletreejs"); - -const Web3 = require("web3"); - -const web3 = new Web3(); - - -/** - * generates the proof offchain using the PII information of the user and associated it with the other parameter details. - * - */ - -async function generateProof() { - -// consider the given information that is verified privately off-chain (storing with wallet, name, age, personal ID, jurisdiction) -// they will be considered as leaves for the application. -const personalIdentifiedInfo = ["0x00000a86986129038908a9808098-toto-18-99123456-France", "0x00000a86986129038908a9808098-john-20-1276546-England"].map(x => keccak256(x)); -const tree = new MerkleTree(leaves,keccak256); - -} - - -/** - * this checks the ownership of the information from requirement (stored onchain) and then verify whether the keccak256 representation is a member of the given proof. - * we follow the checkProof - */ -async function verifyRequirement(verifyingAddress,leafNodes) { -const buf2hex = x => '0x'+x.toString('hex') -const leaf = keccak256('0x00000a86986129038908a9808098-toto-18-99123456-France') - -const leafInfo = buf2hex(leaf); - -const hexproof = tree.getProof(leaf).map(x => buf2hex(x.data)); - -const positions = tree.getProof(leaf).map(x => x.position === 'right' ? 1 : 0) - - - -//TODO: fetch the -const SBTCertification = await web3.eth.Contract(); - -const verifiedOnchain = await SBTCertification.ifVerified(verifyingAddress, leafNodes); - -assert.equal(MerkleTree.verify(proof,leaf,root), verifiedOnchain); - -} - - - -const root = tree.getRoot(); - - -// this is the root generated by claim verifier, by doing the operations offchain. -const hexroot = buf2hex(root); - -const merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true }); - -console.log("---------"); -console.log("Merke Tree"); -console.log("---------"); -console.log(merkleTree.toString()); -console.log("---------"); -console.log("Merkle Root: " + merkleTree.getHexRoot()); - -console.log("Proof 1: " + merkleTree.getHexProof(leafNodes[0])); -console.log("Proof 2: " + merkleTree.getHexProof(leafNodes[1])); diff --git a/assets/eip-5988/benchmarks/.gitkeep b/assets/eip-5988/benchmarks/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/eip-5988/papers/poseidon_paper.pdf b/assets/eip-5988/papers/poseidon_paper.pdf deleted file mode 100644 index 6a48677..0000000 Binary files a/assets/eip-5988/papers/poseidon_paper.pdf and /dev/null differ diff --git a/assets/eip-5988/papers/practical_algebraic_attacks.pdf b/assets/eip-5988/papers/practical_algebraic_attacks.pdf deleted file mode 100644 index 286184a..0000000 --- a/assets/eip-5988/papers/practical_algebraic_attacks.pdf +++ /dev/null @@ -1,6748 +0,0 @@ -%PDF-1.4 -% -1 0 obj -<< -/Type /Catalog -/Version /1.5 -/Pages 2 0 R -/OpenAction [3 0 R /Fit] -/Names 4 0 R -/Outlines 5 0 R -/Metadata 6 0 R ->> -endobj -7 0 obj -<< -/Creator -/Title -/Subject -/Author -/Producer -/Keywords -/CreationDate (D:20221031055618-00'00') -/ModDate (D:20211221220139+01'00') -/Trapped /False -/PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.21 \(TeX Live 2020/Debian\) kpathsea version 6.3.2) ->> -endobj -2 0 obj -<< -/Type /Pages -/Kids [3 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R -17 0 R 18 0 R] -/Count 12 ->> -endobj -3 0 obj -<< -/Resources 19 0 R -/Type /Page -/Parent 2 0 R -/Contents [20 0 R] -/Annots [21 0 R 22 0 R] -/CropBox [0.0 0.0 595.28 841.89] -/MediaBox [0.0 0.0 595.28 841.89] -/Rotate 0 ->> -endobj -4 0 obj -<< -/Dests 23 0 R ->> -endobj -5 0 obj -<< -/Type /Outlines -/First 24 0 R -/Last 25 0 R -/Count 7 ->> -endobj -6 0 obj -<< -/Length 15488 -/Type /Metadata -/Subtype /XML ->> -stream - - - - - - - - Adobe PDF Schema - pdf - http://ns.adobe.com/pdf/1.3/ - - - - Trapped - Text - internal - Indication if the document has been modified to include trapping information - - - - - - XMP Media Management Schema - xmpMM - http://ns.adobe.com/xap/1.0/mm/ - - - - DocumentID - URI - internal - UUID based identifier for all versions and renditions of a document - - - InstanceID - URI - internal - UUID based identifier for specific incarnation of a document - - - VersionID - Text - internal - Document version identifier - - - RenditionClass - RenditionClass - internal - The manner in which a document is rendered - - - - - - PRISM Basic Metadata - prism - http://prismstandard.org/namespaces/basic/3.0/ - - - - complianceProfile - Text - internal - PRISM specification compliance profile to which this document adheres - - - publicationName - Text - external - Publication name - - - aggregationType - Text - external - Publication type - - - bookEdition - Text - external - Edition of the book in which the document was published - - - volume - Text - external - Publication volume number - - - number - Text - external - Publication issue number within a volume - - - pageRange - Text - external - Page range for the document within the print version of its publication - - - issn - Text - external - ISSN for the printed publication in which the document was published - - - eIssn - Text - external - ISSN for the electronic publication in which the document was published - - - isbn - Text - external - ISBN for the publication in which the document was published - - - doi - Text - external - Digital Object Identifier for the document - - - url - URL - external - URL at which the document can be found - - - byteCount - Integer - internal - Approximate file size in octets - - - pageCount - Integer - internal - Number of pages in the print version of the document - - - subtitle - Text - external - Document's subtitle - - - - - - - pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020/Debian) kpathsea version 6.3.2 - Arithmetization-oriented hash functions, Poseidon, Feistel-MiMC, Rescue Prime, algebraic cryptanalysis - 1.5 - application/pdf - - - Practical Algebraic Attacks against some Arithmetization-oriented Hash Functions - - - - - 2021-12-21T22:01:39+01:00 - - - - - Text - - - - - Augustin Bariant - Clémence Bouvier - Gaëtan Leurent - Léo Perrin - - - - - Arithmetization-oriented hash functions - Poseidon - Feistel-MiMC - Rescue Prime - algebraic cryptanalysis - - - writeup.tex - - - en - - - 2021-12-21T22:01:39+01:00 - 2021-12-21T22:01:39+01:00 - 2021-12-21T22:01:39+01:00 - LaTeX with hyperref - uuid:d7a3900a-3eae-4f2b-ac46-c905467ae462 - uuid:3e413900-0000-4a8d-ba82-b67590005fdc - 1 - default - three - 11 - 11 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -endstream -endobj -8 0 obj -<< -/Type /Page -/Contents 26 0 R -/Resources 27 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Group 28 0 R -/Annots [29 0 R 30 0 R 31 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -9 0 obj -<< -/Type /Page -/Contents 32 0 R -/Resources 33 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Group 28 0 R -/Annots [34 0 R 35 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -10 0 obj -<< -/Type /Page -/Contents 36 0 R -/Resources 37 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [38 0 R 39 0 R 40 0 R 41 0 R 42 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -11 0 obj -<< -/Type /Page -/Contents 43 0 R -/Resources 44 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [45 0 R 46 0 R 47 0 R 48 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -12 0 obj -<< -/Type /Page -/Contents 49 0 R -/Resources 50 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [51 0 R 52 0 R 53 0 R 54 0 R 55 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -13 0 obj -<< -/Type /Page -/Contents 56 0 R -/Resources 57 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [58 0 R 59 0 R 60 0 R 61 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -14 0 obj -<< -/Type /Page -/Contents 62 0 R -/Resources 63 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [64 0 R 65 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -15 0 obj -<< -/Type /Page -/Contents 66 0 R -/Resources 67 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [68 0 R 69 0 R 70 0 R 71 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -16 0 obj -<< -/Type /Page -/Contents 72 0 R -/Resources 73 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [74 0 R 75 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -17 0 obj -<< -/Type /Page -/Contents 76 0 R -/Resources 77 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/Annots [78 0 R 79 0 R] -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -18 0 obj -<< -/Type /Page -/Contents 80 0 R -/Resources 81 0 R -/MediaBox [0.0 0.0 595.276 841.89] -/Parent 2 0 R -/CropBox [0.0 0.0 595.276 841.89] -/Rotate 0 ->> -endobj -19 0 obj -<< -/Font 82 0 R -/XObject << -/Im0 83 0 R -/Im1 84 0 R ->> -/ProcSet [/PDF /Text /ImageC /ImageB /ImageI] ->> -endobj -20 0 obj -<< -/Length 1514 -/Filter /FlateDecode ->> -stream -xڵXɎ6+֐t ҷ =%O")[H7 "YUN`J̹L7|3~l߿L~+se:琧11v3ƧR'WW\jh g2~1_9ñq/N?|Is<T<%t(Q7>6ɜ d7hG a!T[sQy? 5s1Ų#B5:VMP'~pT+FnRёwU6FCܨ̱`Du{ pc /bS*t=/rR̘+4|Dg ,jVPJh@~`Bu:\L^oH]2s۞(>ߺTK C #}Z{R+Jdl#~;-3Jυ5M' Xcv Xj>f>ߵ>iF^`EE(@@k ; ~WAAMSd1a4gv[Yz7QKI=VO&-et"fxO|;M[pF?v@2PW35C2gVu&r0c^j/~HOf4bk\#ʸ:,g"Jb՟ HKx*k)whE卍{;սI\{NXȞKo:JvV'7z'F";խ7ʍ8xfqy-ұka艥, CAf3dFJ?l TXO>MǾ.7Wwಐh.%#!4pz.:p*\!\?s"3miǣN -endstream -endobj -21 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [0 1 1] -/A 85 0 R -/Rect [136.663 237.241 458.612 257.699] ->> -endobj -22 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [0 1 1] -/A 86 0 R -/Rect [192.022 660.868 193.019 661.864] ->> -endobj -23 0 obj -<< -/Names [(Doc-Start) [3 0 R /XYZ 72 826.58 null] - (page.1) [3 0 R /XYZ 71 827.58 null] -] -/Kids [87 0 R 88 0 R] -/Limits [(Doc-Start) (table.5)] ->> -endobj -24 0 obj -<< -/Title (Introduction) -/A 89 0 R -/Parent 5 0 R -/Next 90 0 R ->> -endobj -25 0 obj -<< -/Title (Implementation) -/A 91 0 R -/Parent 5 0 R -/Prev 92 0 R ->> -endobj -26 0 obj -<< -/Length 1779 -/Filter /FlateDecode ->> -stream -xڝXKs8 W(F _)MnMmflMd)+~r,i;Iă>@<ѻ#룓sG"cR:2"2d\i}riYOeۛqWV@­s'B9/wۥ|3k%RB$bi]tU$Dl O_sԲjأsc"TiTD 4(,^W#M쪲A#!2Vd\Ug.4o<]Wd,R r%sg 9q*Yatyr]4hVj Pr&tʀ6ܞ -9Rn&,i0=n=f'}` ODB2Jef/nz;:%f_g;ZS4{Qֵm'ڢDb$ -kmC۲iZ(a_ܰm. LDܠ -70/ 0o|ONs2v Fk+ BWy f^gAkx ή(deD3Ԧuv`M{OVtn@Ev)EA% bvws-mMx[8pBAaJ KA9+S“5R%0PǑzz?.g C֏M| -CϪt߃."a ς9n߸Ҷa_'c6tnIP Ϊ&ַˁ ?C<T=edrn/K8 ךv>L20d_eZ腅s>j -r7J8kZ?WFHB'rz c _m35>tO gm$&18X?8x'*XZ -nPYyhρ̤ـDa(qt"la!׌Sjݲ7U~N >,~`BBK;B?'i?/~ 0 -ŷ/7"qSmY8w߿:9YxpbUs۞磦uʴc>: 4h? 8{7$!)3E'o0Jx+=eOqӵ7o G? %J -vInW1>yOK -n\{7" ;'s\C,6(b <0.5iv813 #pp{N5895qnY(SDFɐ<䱄M m[-ڵ֍q* - Ut[Ư/O-e3?5Q Nt(a>}#H >O5$ˣXꟆO2i7؍b)7_7H&dKee -xxr -F2h.h}}E`K )fD6dN2 0/\/\ځ -endstream -endobj -27 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 96 0 R -/XObject << -/Im1 97 0 R ->> -/ProcSet [/PDF /Text /ImageC] ->> -endobj -28 0 obj -<< -/Type /Group -/S /Transparency -/CS /DeviceRGB -/I true ->> -endobj -29 0 obj -<< -/Type /Annot -/Border [0 0 0] -/H /I -/C [0 1 1] -/Rect [277.073 337.718 446.437 348.843] -/Subtype /Link -/A 98 0 R ->> -endobj -30 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [0 1 0] -/Rect [172.732 260.892 206.992 269.803] -/A 99 0 R ->> -endobj -31 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [0 1 0] -/Rect [172.967 112.708 214.715 123.038] -/A 100 0 R ->> -endobj -32 0 obj -<< -/Length 1170 -/Filter /FlateDecode ->> -stream -xڥVKo8 W`U/;-vavn$FCR5"(G<[e<;?q3 idf`+n6, O.:sdwgLUeH͖ Xae6[d'J~po&SeO3{WGp* 0[`Xf3sP2]l%EBɏ$g/V0#w^O=oçK1k6Kn o݇ t5H Tْ .ށNr`=̙+lJ Hg("OdO]kWi[~AxH&'hk]GmWݎ>k *c Aq < /t0vz\Z1Cs4MDYBh(0SJF#0BOޠ+@nlL R]C$| @XGեoې҇ᐑ8=4F!929 Uj<%%Poڈ*yg%1glє lZArDDK}s˒n{ȸ -(u +wj^5)6i4:»-pz -y߸(]ҙy[C3GGqٹq@|1$jMĻ0'K&%A٦c" F,^6^V@&L@]@  -6)zL(@ux[l;66BKDoj(had7 &0w)R/̚{5>D7]}9}!.j}͸&߸]W Ј"/h'+xR5K[ExXCm -endstream -endobj -33 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 101 0 R -/XObject << -/Im2 102 0 R -/Im3 103 0 R -/Im4 104 0 R -/Im5 105 0 R ->> -/ProcSet [/PDF /Text /ImageC] ->> -endobj -34 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [0 1 0] -/Rect [150.924 607.596 193.282 617.925] -/A 106 0 R ->> -endobj -35 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [0 1 0] -/Rect [202.777 470.069 245.439 480.399] -/A 107 0 R ->> -endobj -36 0 obj -<< -/Length 4098 -/Filter /FlateDecode ->> -stream -x[s6_Qsεs9w3Ih9'.)5u[|(H܃Mo /o^|yd%fJ$ ݬf -|A/rp定/ŲffTZJ`<##U0\l9)<}+ߢkֿ{Y?mM]CçnWm}y:o{K_Ρ.{v5lT.iAA O*41.SH]Ha6oBw2}2&i2opHDF@"lA7ܷz8dQIRޝDK;zz1"Ҥ}>dl+#ִv&<5Xv ǶTrsPrWu'$eJ#tOr4CNԉ 9̗&EzN۳+Y+: 2!7ؚڷY`Nc9wH bh.t/S_T L_Ek{[uێ`*S `Xby1Έ̑jۭ2ksOL@JL |BTdxN8#8$#0H둆".>2&rR(##+# -I/QZD\$+ #yG޷ ms J^;xAY 41|y":O06'*/8{Hs|Y(;=nc;8́lYyxRnՠ`d wz}mC p̵(B"bYn-pՏrهyLWzxdEX\`X0O+z3XRљˋ,Ҿq@DwOD@:g`E@Qqd1(IN`"O >n[oƒ}nkHT߲̃ MȤ;O>p0HSF9xiT -|^[0L9C, &s$<1aa8_01ά fӃC8p \8qf5ŰV(Zբ\ E ;Z|XQ.#[:چzIӕJ`40O|dW*Ȍt&1[*P=EWzlp$SN$R2 Ktr#kw`Ucc: ԱrɕˎrɬD4eumID$O;=l8j"7C4<@Kීg] 3"u.SP|&JJ R3s)D<~o]kcs` 1t,9<'6 WzlumVT˝OҢpu&qZ=[`$ϺyX1kܴf/{.&<2,pZ(xROKY&-];b#ã(CK$빆p5m.gi4LjwW y'Uq_mթT $tfLJ84bCWwHcjY~Lk2=[HwtQ\FalU `:D]zmuk@eÌbѓlDGDzldz*nIȰۧK~[7e>({GK$hqW.apݺP@&:% -ܖ\7 o{}U,G> B~wTD,ʐ!QFSFi70aX b<]?[i"cHB5b)gsr?'gsr&?'1ub:*vR["37&wL˙)' %,؆ۧDb -󽁧STA4I!lJ r}#&7և֔y&8_Wfn1Au_iblWꕽJt[AE'LPzC1TiW"OIk;!{c~z >{ce3uG Rg+Y -VUTl D8[{/ܑQpg9?vg g8}9{ȓlaZJ߮7^ɸ.T.¡E&) q,AD#%^u]80 bj~/+𹓱~gFT,tn;Xn&]0h$7fSnH>ID0^ -Z 򸪙TmO1X=kYU'@_kSH^vO]Hpezrwev*.$S>{ilui,VWx1|~O <&5h =MyxBg,̞ab)9fhCnCG4+s:ihq&\*u7A9>h_ -_^u2:ߔg -G~nhkTL -;_+(~~ IoLex9*jd?[ftY*3), -*[f6f㿋ӹGx &:]튂Sl9d̉cg7gXn -p{pg] ߆21Vl qk{Jgwd6vbq/wP#(&(n :"zoEw[ q(`˴zo-fEmu״~GGW'v_^v΃+CM>abjĤdj#Lоd*MALG"2P3 ӝXX&}&bQy>Y's1#&&_Ea|JėdK %,pY 3(9Dl Sʶn._5L - --7K]'#6I6=qE (acY}8Td dvqyk@?P3[HXr'g`U:B*?=_t$(0:Wf@4֗L*}-cW5SZ -endstream -endobj -37 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 108 0 R -/ProcSet [/PDF /Text] ->> -endobj -38 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [230.949 459.553 237.923 470.346] -/A 109 0 R ->> -endobj -39 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [0 1 0] -/Rect [213.764 260.242 254.569 269.098] -/A 110 0 R ->> -endobj -40 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [361.668 233.841 368.642 246.434] -/A 111 0 R ->> -endobj -41 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [199.778 222.439 206.851 233.232] -/A 112 0 R ->> -endobj -42 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [261.098 222.439 268.171 233.232] -/A 113 0 R ->> -endobj -43 0 obj -<< -/Length 4812 -/Filter /FlateDecode ->> -stream -x\[~ׯ`chmx3:o|c'-NkcɰIKʯODwsFJ4&nUW~Ao^/v$R0k [5c._o﫷+Gx,j-^>/[UYOy(jw_5ח7X\ĔE_fqKSutbm&quyZsbYŸ.1٭xpRSyƮPZ(m *gPQd*fBK̆:\ڒTmLA dW.9PeiwĠޕ*Ob͇)VE)`t`EL+(PT!1k20`$1~;_R KD=9M/PjE -ahni`%H}&Ā53qҩEf`zmnZ\ZOjz,#j>d%Ffg >0z(}IҺ(\$P9CUL͋y\ dCwOBH -z°Ȅ+;l(#Kk?,(EPՌ?2{uƥ*donK7/}aԂBj!))=bdh)dLA!x2L1ڑzp( 22tq Mx#rFIiJlfOp%.9JL/_ -]cȩ@%DzЌFx*wʴF. -Tw, ,3lْY>܁Ӿ] |[C08Mÿhj ;E}'ʕ!X%dRxϚ&Alp.W0߼{N&Hb>=A1\Kp,;)[k# -aF4UDRDmcgvU53;^aGbg0*d,jݞuڗTQ )t'oT$FʋKY^_\Wf&etպ J^ͫμZM²C,(ΰzS#6k!l -6}ٞ-06tcHӟB3w]uw0ϲWGj05ۏc]n[1acy:Ns~k}YS՗`qݭ.$9ob[/ -skx#*nV_΢] h!lRB}w](|w;G,]|y7fVjk:nH<"p;Soh_OM@ YOiک:49|``Xص.ߍX$@l:}55Gf-9WƑ`'",k;,{tlD: N8%*9LWy>u@% X© <$[ %ןq1>DzQ5<՛,cT;dR'Zt›!Fwf;j A ̡8K5"YF3\D%aZ4PaK] R,$*EnLX!T+RBp&3Q0۸T0$lqO+F_`g!(Xs2N\ЎɿpmW,F#PA\#&I^m | '& #/]z#+TKF bp"_<#x<Ɍ 8݌ʑ?|,C6dwƗR2J3_48)>hlpxw›XTw0nŷ]epui)D*ŵc*V@nM[Z].="µ;d`b@-;7U}JLӮ7 FTa?V4*%ߧ]%B!^U9e|&N" l" -n BUstCRw*Lv rY1 -~pK7+p٫ ^R҇l_ئo8ƾM<`-`rXQJ"\.&aPGCʨ)$# -S8sBkuE6QӻXu#CGgV%x0vIY?ʊ >mxޜP`>u -"Y4@E2{- /,RrJᐳUwyV7h9ȋ5O$ ubDa;l VGkQOdqA4X6(ƚ j\ދ?OTL<;>wg"kis Rs 89A }cu8VM$)*xtv5tx -P e0,GD"=Q\Eϻ $&_ʔ7S"(ٓBU2g>ϾԤ5f b+uYH~xfVƹ/c0S[G$ vrv]ht}銽L(iKR/yμ͈ZHF vm@KeER}k\ *͛)wAxضa?޾'0 -endstream -endobj -44 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 114 0 R -/ProcSet [/PDF /Text] ->> -endobj -45 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [146.783 297.523 153.831 309.478] -/A 115 0 R ->> -endobj -46 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [380.664 297.523 387.713 309.478] -/A 116 0 R ->> -endobj -47 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [138.048 285.568 145.022 297.523] -/A 117 0 R ->> -endobj -48 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [195.966 195.15 210.944 207.105] -/A 118 0 R ->> -endobj -49 0 obj -<< -/Length 3776 -/Filter /FlateDecode ->> -stream -xْ}bg&ىT*%帢"gWXIKtC ySTi9⡠ŋ:e?-X! LpGb}{P/ -zCpV [7"1N_A8uEh^/E:GBPJ+fkB$|\.-7_~81Dj[ nv׳" _D:ի Pf(,%>MIb,/fZ BAC0p.[sg3 j"Ɵ^%iּqL0=$ôhǼH40f` ^0i¨8'j{f&B@M&r4j+SE&M,pDNp( ab0M1$sN2f/UQ@AG+@xpEE]`3O;]p$b}.A1DX p*%10C%_7;8ǂ2%$BZemJ~_5dc-tL)&vJ|B#<ԀG-?gR򟷖{//pQW| .3v -C{eyB?w~:'X(UN ٚJ]ma2&z"Pg٦6ME:+;zKS  z]0y̢_Jyg$Ⱦw} -)P."IpYC)~:T_( -S&!:ihvM"}&)933b!7H9|Y!]ESh5hТ x2"K1& % )} )rLV*ۭԀ^bȷwՇU\ )C oMx -[>6[̾ ۅa \C;'y']U!'K7~H} WF%ʿV3e +bp7ѺZo:(% -I) Mi2.CYAxVo 9 xnr,ʷxȬDfE̊Qi=NGZ$6iݭa>(`L)Y@NKPGd|ҧr{h|/Ph9(L fVA3ﹷe$ I2ڜ7hx޿ WT+tVz* ɭS^ZOuZQ9u"GO|(N7 Ԩ5G[{lP-*2.lr H@2ot[@gt *UU*"J[8!{dhz;P?o+XԘؔ?VXr:b&!tMF!dDB`QbZ@'r#Q~v8q -b&^ $LN HɱB$Sr!Z3\ 6q }Lb-ی=@ƆTPDhb.)bwS0?d{%%C>ω,LdElR{xDw"%ʔ&# |WU#8* =pb7dseR%}JOh_lIC)QD)DzȈBXn]o$Ҽ[C >'M-DF a 0.`SKl$1sF5ԑS9F1ԔK -66bH^$N.H 'Oh$gܫ$:Kp 2m2Oi ˜)0Fkkc@g(d=wB ,9} ׳X1ZԺ7^n7J% -]pڂAF|; 4ن]xٗ\VESF2< u)_WaLsE+~gۄZ[LY54~?ɧO?p\`ێ%"yLŦWM;Jv;=Wb`@<>#o*t"U6\vF$M!>oI&ڰ#}LQczYݲC~Q - Aݷcp+I6=9x¾(o}Ա bbZʨМljۙAء}ev[oWձy1 X(׶y -x@zhk iy<#p} iZ#ۢ>y^G!x7n -ɗ ,=@nu~n2Y`?ޫ TYp]0Dd]+Qz?Qk:%dɀo&a;E2DZuͰXiǮb'懓_5 Ѡj-TNal'lsX~!n7Zw)wxdCqTL}r1tM3gD cU# ,`PZp#vLЩ{IzM+n)nKon{+Qfw+yҤS젢_.G/}?${(칊y&S )9>b}{^8#Զ m6.jC*sm {l8P'N8)M$ᬋy H2gst:Oei>#4g(f13] %' RH16;.SEt'$Rڜ³:QX½yu)ע; t Wr˟[8"Ky Դw δ Y$' J -endstream -endobj -50 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 119 0 R -/ProcSet [/PDF /Text] ->> -endobj -51 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [360.984 458.986 367.858 469.778] -/A 120 0 R ->> -endobj -52 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [336.664 447.03 343.638 457.823] -/A 121 0 R ->> -endobj -53 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [294.706 239.834 309.301 250.682] -/A 122 0 R ->> -endobj -54 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [139.341 215.923 154.318 226.716] -/A 123 0 R ->> -endobj -55 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [400.937 179.504 408.01 192.097] -/A 124 0 R ->> -endobj -56 0 obj -<< -/Length 6031 -/Filter /FlateDecode ->> -stream -x=ks6+Q $vrٺmغȒ#əƃ@-W$otFSTME?~}+j*SIe}EV+#+Ee- ^_Wog/sl|v엗drs|.6a/]j7_zkM^ۥK!gWX*S7Pύhh"(Vk̕W6i5#$#nvhT抵 ߀9 -^#Kgc~qX,`1'LJi~:3cPj -y!yYa.USyUԂN'kҙ>%(ՕXݵ_@s :T\L9(HAH嫢Di4%moQB --sJ쳒$ Z2IQIkWNII9'ZEM~zS]c'^XM &,QTS<8??IτYNvoitJ(KDhYٔቸ~=+8^h'}]6f'3;ޘǚ)> 95t>Ao>͚c!S4'D{x&!1} 1=_r9C 7 N8|ڻ^lvo-f#.d|{H b Q7MIс< p~apڰi_k-F> L&yI3)S{׍FiZ3+njc䤃K44!{}mlD%װ^]ǿcmtU(a ܗJJrU3&z6ƁOXb͹aJ\xIɾt$jA#\P=G@4| TUa*V3Ę46=- $UVch!S4# 7;|LbTc'P1fq\%&U-.3Ƭu9m㴁`pL.d漝ڌK7e܎ЁIo -8J;BH~DШ01lP2FKtP1v|h -΍S~n&P= %8l,;wi|grzqZݥvmL`&/Er1 +S,L -:$$d"1EͰ>w ]2c)8ЍhjBZ@,Ǐ^y -gK!6j>q]?\m׭65}?>#,<56s2=mfEM;[TMVsk%s%h8w埊-\[ lZܐڅVyo%0īn A31(>T`/jï{xXVۍe lpnX+Y\ Sb!cur_ИhZt淥E1Ѓt̛@$Nhv_ -^Daq|޹o^2.DdӀ%0J?9b̳iH -ٹn1~ ->G2BqPy`Az4fJ@DpIYq#UꜰyƣEX I),$pV짏 JP|J~f {bwtz.U@\A;]XM-P'fLRWAT'`u,ʧxɜwt:0$Xހ+d]8%A -XZ0!}$}uH2RۘRm*k.ڷ)*Z9axrV߯vnz*X/À;Lhه|fA6a<֎-WO%PAPa$$/,"ђ:eh5̶Jk+au|SBΗNNm NTZZ7Ш΀rq9n" RDqD ~Ç'nFAkuq=wOɑwݭFsk.z43R8tۑ bUF1yp±δ X<45O{ԍn8X?0OC<=;%1Rg z:v.Uٳ{S73̈YgTUD KMuk甂=@=k9s>y:{Ӊq>X_9ɷ3 B|{\O-=S믪ַG2}=vWO!(\78rщ>ŐE =o?sFG JK `9w7` -AWm9\ømR2wD~ p۷"^M\lg)տ32ʭK~L{hP!Bdsn7@[SM^zHM-ūf&C! pC1't @ ̀ -1 "Sϰ0?f?uLmrĆP&*lv&&6`0 cXko,A$YK_⍥B)S{Ksz%vGm/ҘQ5΀zz(rM`92N?̕51^2.wHLXej-W(kAё@vȀ}YMM ^/3I֫nD7`4'_N=g;oAmnz6u 7P $Hs΁!4M=deUT[H̴YRAP< A.ȴP^\.vQ -@֤/w)TosaVQIEX| CCZdx<]IB/8AQaR;zT|]QɚuUo -JG{1ݤnR}ibuȺqBoLlC8}؜sJ8v"6.Šsl8Bd;PSDP9OM=:9o(9 Ӊ|P,[P C JǟUDQ֨Uo 6 pMEJ"WM_gZOuʘ<B>z]l<DžJB1VhwSS$ / @`I`C$03gT[r -QFa`BQΨa8p}=ʩ<|=}O1NMjO,4eJ4Yj֯5o0ZP[tmm(jp<+'9޶ےQ$9@BOmD@]zUG!4̋IYq,Qfn[og- -1z[xJO@>ػ'=~bo Rw%II-Jl P %/&?eBRlضs/I:K/hg˫h -.\ly6)  -U\ -ӊ [1%M䧰3;nsISVG(> -X,.GWxڷ5uqgoz]5Dj{zO+8kQk6&u ET ^cYı=5p]9c#L,2w}u5aiyàUp'EEˮH፯t.㳺hXy05>:>˝4^!MU(Ո|33 q8dvF㣌PΡsVǕ1SnT8>t=ҐSVr;@s;*.)vTsk*Ewm.1/adnRyi4hW?f}-[glu\R(*i[fd5@%|zx<_[Ǘg1΢f&jN$8*I`9jFKm±k=7oMl޷v3_Ϧ˻~ؤs6+lalĤhF|s.Gi׻zpɈf_фMl \؊db?ҰEHN;RijŰRKwэm 1㳱0,Nm1ћrԴN>{pp?\R:q/CSTWa07d{nCF:Q-ysރ[ބ֗si6렼}wXHF(f5kƉozHf˛k.H-dF@'i2؊dWzP]r0_VٱSt=Eg~gڅp~޵7+jnrs@^ ϻNcL#ߖnfXnnn?qȭnZBdIg;d~x:{w/Ԟ5* -c!+;L"m[٫KM|o6kqN潴=\0!?ăHy60Cj c_L.Njҝec -á LO[ɤ>x;%X?,п)l1ָ_bDzz]1~rp)#-=I"pʺ/vhc]W:K" l+bƨ~?0TdS{܅͕}\xDZV.mDsMxq`2cqͻ7ΝޡԀA_7d_.̮WlY6za.7R[L! ~mBƞ js Yk)P -endstream -endobj -57 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 125 0 R -/ProcSet [/PDF /Text] ->> -endobj -58 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [227.887 723.204 234.861 735.797] -/A 126 0 R ->> -endobj -59 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [306.129 557.995 313.058 570.587] -/A 127 0 R ->> -endobj -60 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [360.984 162.079 367.858 172.871] -/A 128 0 R ->> -endobj -61 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [341.206 150.123 348.279 160.972] -/A 129 0 R ->> -endobj -62 0 obj -<< -/Length 5355 -/Filter /FlateDecode ->> -stream -x=k+x߸e!qž\Ut*_j2Y2|0I*fi4Ѝ~3t~Fg=7𭰳4ٛw3F(3،QA,3b~i|au7??|˛!$Rs6Vn"nwz񸼽cJE$+|n;1_!)¼՝q}w9nO{$FnǜQX޲Vf0C_W[)N1J1 ҘYq -B2e\Wꝛf9vt8߶znPyn'Tm"N7wUүMO|ͮ^iPB"g8 2Bt!(J, *no?zI,)^ލ ; { -k/>\;p?^%1d+wUaBej$X1uޯ ZmQ䅞=ܯ8P_Q -{O0H <\Aֱ -| ݵ8oVwNIC$gU1RCy;6|s9ųv}?~Z|sŒq nБE0/eWT"~ܪK>6 ?If{Xߡל@2W0y>Z2jL1+B;L]BTYBn gTodX(^DQ$`|B'b14)bU1~lbR A8 Z4rHɦ!p# y&EE4[=ΉbfÍN7Ă79gT+G@m:p)xix K8pùV8 oS1~/k-F^,ށ3jw~M+0^& -$>fDze!Eֵ&"Ȃe݂'|ԊI8I ն\dAZC &\.n3.Hh-cFMk(v]v}9o'dF0pk yS_vl NS+2bjɉV,Xwp|lxջY^3MRchQc|(! >ڶ,(tX]?^+D pg&>FtmDc(]29*NL;Nh h9n'!x}Xo޾?Q,S }ଣ -cNGok]5 F|N)%M{?3n"$6YvZi BHQ6LRx)irJ"$J$IЄl }Y!n*3v3"AnDbZh"Ά7<]@+ ]4%ֶx(/k~Yg1zc.5-b [h>ylM֘w}_!!\ t)~QjT .{Q4[7%pԈ~Y" Xdskp}7s6Ui1ڵ⃀ 0%'v\,Oo7|`EمvM"P*.l Bh ǣ}_3 }2!ORK<§ 0*`NC`N%M/"TƋUx&q^ -^D -n`tfͰAn"nHk)8qnD["(q܈D@*Q FMRg|{M64CnZqҙ8QG:&u W }2YlL- SU{ՋDLRe"E~?RļE8,avH,|振taJP\r=j; E_#v!_`O5Z 7TCT$!"VU ptSkQQ>7gy@\)~ 3js͠.lloz9u4k+!0t*D <зz *@']ƃo⧯e)QfXb)_f#&"eKi F -VSS# pQ$AAUE.@&,Z6l;גԣtepgs(jăm|-QVMAߊRHSHn+OB̤ \d ti@6SbFeSچPqyL1n"Дy(zni $Rk/Y(Ey0<4dBzӭofD7EP 8'y6;wjq|s˨L$Io33*St$a8_ OkF0Sv#%n UUZ )TLt.u7ʦF#l]meݯdEc 4M+ߡh{{-|M}Ye >yUh*u^j"; ;1Lpfz Q&{m  -]t'dPZ-4$*~ClV{TBjEeG3B%LԃYWF&Q*] ET{|hϊ-@"g4g39j/LjG5$Я@(G6BĶp=M%RMv+}B뽄fBt>"4ZS] -P{/m -ys LPDU"o1U(uPr˕|IwFo|9L2̴'gP\_5A3?EUCFɈ?1oU7xұp@ -(/W{P_AkpcN0iGa z&d.;LǏ11FMGk}ԇi<藆u4X!sD\?~N3f $`23;e9o7zТ|^ E,,J#fsoR;xqUiO?ƬJ*GϮHJʖz?gyghɢgƵKۣuO=~>*|LqZ+'MrXOwZ937it;p`r'&c&Lٚ~bRZ.9BRUXxfhjC4z;64督\I"Y smڧ픠dCĀ|CE~4>Z=`4EGy/k) 0,v~8*)*G|38I)G7xYy),])- ՋKjYGŅcwv,W+<*.*G|T8q)Gm{{d⒂_x]@ƵWkR5.K1p3̇_5~I#D9HE]IT.>Ln18T QJ * 6*r3W@xG׌Y~` -(|8e ҦHRXH_k\AQP(-8PnZ+)9ʞXWԵdžiGT~}+_+BWUJKfў"iT!5rwS(/)I"nzmf CT}ӧzب!òMrw~f)mɎ2uc}T^;a+ľ>m_ Uy߉UJ&*[i4mf}WmBR_2< ݯ[.t JQ2_kȰPan7>woBiWE 6&{Xz\oW\݅pkm^n5TpAf@*qYЀKm!#aEYW=) -fbrTXǀSR\녁v#'GNh.ENg'蕺F#Ah/_No f\o<A_L_ -Y*si6YVNM0_m40%p^K(?`SO,\`hJrRU0Q i'p9۟\bI}Lj{܆gwi *E 2=\2CibA^j1"ҒCV ;5]tds_Ґk 9oxF 8SX<ئ6[íيY67> -endobj -64 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [146.04 472.626 152.984 483.419] -/A 131 0 R ->> -endobj -65 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [465.129 102.303 479.89 113.095] -/A 132 0 R ->> -endobj -66 0 obj -<< -/Length 5057 -/Filter /FlateDecode ->> -stream -x]Y~_я=!5Tj7֢w)kc! eI͙L;fG}w&*9Ę&*qd"3e`35oϚwg_avfUL}QP ߚFL3EwzsΗo77/Vv[|tzsIP~:Op -7zׇka)(B_/0PA\y6@/ny"X7hdц2X&|?-{ϒaJ߬w/q{\K93n*o88Kq |c}Z_n|ue|& ]]8X];Pof?;BI7=897RJ+`zV>lw^TDY=j EQͿ߭oW9fhI8 !ύ.@@d;<C$ V^Q{~e7N>y}ENz|P/Z:`xB 4([/A Í$id&ip&mm*L7r p&B'BC@üs;0R16R}Lu^@ucC#5f;}V|Uy`Ym(]N[ 洨L_^^^BսA2?*UΉ`2&*YPM,`WjX?wU6ڕL_Pa[hBlaOCMA?C&P'y2RuƲhh[kL*ƁtCL~f)b_eZΟgc͔ Xu|FY[ѝN# bTiv%YHr~[M"$>^6j"[VѬBLn"h1@@W }Ұ764M/R|djsQ43n+и#{&}I9)}I;{ Mޝv摵$fBU}q{>bC(ёbHdf (n!¦=N (2 3L@f M 6d0 N* h?ƌ4ʼnTE7.*k"P[ 4s4T3BYeb0u04Xb ܊# 8`rCY(ȶNp,\n.bT>ghcZ̫0\$y;>Qpt 9u"bE\ -rLiXnn~`(H@ϘYwhcZLq&m1m?V&O,?^࠮@BVߍ>s} -'u2v ;2"oivHEΚc {d>ώs;f`#6Ԏs;a{r ׻zl<$Q?RNa)ci6aK0]J)R[3s? !=ǎmscF&23YoVRxFW;`: ~c/ ٭.q/14`S}MDE=XU)ڲmr LJ+(NŸ-msA[Lh;݅˻䄝h%SQfm%J?X'e7x.A ԅX̀ۙ>sObA@4YP+5 1D4 $h -<ςK_haL 1F,QD-~Hm -kdAi ţd u2RsYR\|G6|O D >TTZR>K0vO߂(z_@Ls$*uJ]!xwnv͚DclaH$"*$ۍf _K7Q -RNu\5DZd4d3!H5.ے9~t)&Xt&LC1Bs+U7NSÏ0R:>$%\R~ 1fl0?qPcCm̍J@u#qLiˊ:H^(a'EZtkwC*>!Ԁ,(DxmY qAx4iqDR#i*EPNn-'5p7ބ[ -R+0Vh@5!ke헬: g:\x%4cx [@;v[>(`XIڋzb0ss02d&&B}vsR?z M4[Vg9XB()M9 (c8I="7((lZ?.+g2s _t%a<{:1F {@i{@h pANOIndB<?)x!G#|fp f ;j#\! l\azBWJPQxl - IJ[I8_ϳH5 KJC9CdYY`޶z; KO pKCKgh -wOq@^~ΰS:-p. 5!6qaďgiG`R;GduƓf<5Ov#h;(ެP˜`}ֳ"\}gTۼcM)z$Uh Sz0f34OmLݻfHO hwڸM8ץ7+# Uv DE=Y8ŸhLk*̅6~Q*xXN).xīݱ!+T%D 7[앀j5z w{-m*DJpED?4d!b`?FrTʈJ [x)#nA+{q?f:wՍROCMWmEŽ{4^^^@ $GONvV2X[`AVbK4xQ5)p>M1RBMTlMi8*;-=HߦfF*am0*vaWe,Cҋ -:gsĴ+ʼnJTz(d '0sn:4%(E0sCUͥ5/2 tn -0Ї(Y GO3J_1<1@@ 6*21ӬByںSKD@:$D3d3|Ti8tQ,Fi@5ʬmq2G\vS'I5jۏ -VO>+ pfSYy=>+|IaRb"5D< /}dIx2_m_߅[õt޾]>sZ{]TDXs/Qn^m/Jm7az ߆fLsehn'.CeB*]%uU_L7?=!Nh6jڅq7cif-u/i¿Oqi][]n-Ybv]>*޴WQʄ#wp;{P|GRSxBӟP}:6=[غ|-igV jB1Y5Zp<}E^}uNg9j/ꩄ{]?<_ hҫrG*{U28]кǖ:g -endstream -endobj -67 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 133 0 R -/ProcSet [/PDF /Text] ->> -endobj -68 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [428.386 685.99 435.384 696.839] -/A 134 0 R ->> -endobj -69 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [430.684 661.526 437.707 674.119] -/A 135 0 R ->> -endobj -70 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [458.106 532.409 465.05 544.984] -/A 136 0 R ->> -endobj -71 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [321.44 135.689 328.434 146.482] -/A 137 0 R ->> -endobj -72 0 obj -<< -/Length 3378 -/Filter /FlateDecode ->> -stream -xr6_;tvfMi24-2RRJ=),l pp7#2z~Fo`(K3vDiH3g2ɓslƍVP.Dr՘Sw})nu ɪ_e^bL?\-߮_~T=T5KFR4XL%ԤJR&[i2ASI`]@#ptDҞń_C$]TF(8qP^GPr"w`P#>H3CE`]`"ARu6!@HB -XQaIGFrj)R.{o) pY*25ߣ熏zzGFwRG@2)`8*@DO%Zӏ BIٷ~ँJ()$f[RC;ھ\Q_,̽S/SjUeb`2j%i p:[}M2`[鮨i&5㗨W0$8>!a`/g˳TK Ĥ\W]ӌs797ND0yiOtJW|&Smq`*eh>iEj="?tȢ6(焥$ca<"!e#F I!Q!.h_t~&/E\k0tP.r,*Cezw=` 3fųH,gM h0aJTWy_M<~Q#EU/:~jh,BwnX$*WbbCERe[:NRRs]%ɑ46_\nƜ&[d,9_b %pFAg,C/7w1 n03jM>؃IC>-lΗ1˒;T%iqr -~`tR%=j5Os|B -wfc7~Z[of*|T~@i[ -V@HuD!fZCns͊y],N'T5;r,&_nj9f:.W{aX Bv"O$!ɋ_]0EEN=ִBo8q)C.G>?dR#>Y5ynn翥UY;w䝽i R!`70[{ "bg'܁\jaXw:|Hu?HG=0 kVնU*k) - AbVf[6;t2`X2:(h3bvUO\)7v~t߂Gߣ;|mOs7Eu4- -rj-lmZ2ZIH D*xBd\a:խlTy@;z 2>O.HX |COC-xgeRد -s9myEAlPC$cgv,2RuP38 \ Y:am+C]T8ElNpz$0TuE bEe/ -HH*ly7$Ol8 X$Yz++&HڈxF̤M@agOrf1/VZ_őJp9e"xTa{:a0!ˏSy]S6<)TMf-8Y -8l{\O^Urn˯ǐ#ڹ~H-Lëgv Z&4(:D7cܾ ji7.Gh,<x@D{l>NszHph/a^q -{*p/`* zwE1&0./ "] -|ɭ?0~fKC4yPmTwg_zoe=5){CgA?;vgzm2 e4!UNUB_a*LUdʇ6Gy qKboc\cGTM -x{ou՘2Mpm0nm#mC/1a":yuD0,j{0{Quv}\mܳKLjkU\Ua߯nY W~6:6E*fryr8MҢ-H^A]n,grmv[Y 5vŹuV[ զ/%=fFk{ -I<Q6ܶDw%Y]jnRMI3 nzE,M큻Iq ˊD&|7;+#R -GTe)l4ׂrIᨦKr[g+sj廴W&S4BMgD Nk8~ Ҿ 2AI+m*_W7uE-OZ\5ԿKXWF{r^/Qڕ Kˮ-GJ kg8Gi[4cF'WOE|ІK_j ~U^|}g/4։͝s'xyfwQd6\Sg -q]'^J֤\0p/Xc\{:ap[=YֽC>/X9ϒeI1+7yPSM{߾8K\\L޽v( -87Z&yqy߄ qC _ۭk©x.[of"QTVC2Hδ߮!ycQΊ5,"%;MS~{b`a_W -endstream -endobj -73 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 138 0 R -/ProcSet [/PDF /Text] ->> -endobj -74 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [392.004 455.725 399.078 466.518] -/A 139 0 R ->> -endobj -75 0 obj -<< -/Type /Annot -/Subtype /Link -/Border [0 0 0] -/H /I -/C [1 0 0] -/Rect [375.158 443.77 382.132 454.562] -/A 140 0 R ->> -endobj -76 0 obj -<< -/Length 1391 -/Filter /FlateDecode ->> -stream -xڥVKo8WHakO-M4M(,vfl!dPR I1ؽHCΐofX6Xv={sv)U!1LQ^-*Ͳ{r&~RU) -M%HZp VG;>\e+΋S&@bϸ ? Z_HqF[?޾dK]+:i>of$?PLQZkNn],U˃,c7/-e;\,|m` -1dgM|õ.Z?c9=RܶFkh>*Mgv Z̺yl6Sz6Q\0g>*[Dqh}6i(rSBsfs֨7yIQ#tIe'KUpxzō(ȅ߬!h}l"|9}\wcx8b5hH듙`q&M21Mhͧl+ydy~PS{,~ݽ;;skpz#N$Wvy̓0Sw.|>ip卅\F[aӓQx<IC|g q>9<%X,I|TLlWw0/hb yb'x%{gm$8E{Eyi}̃Ԕo>׌㇑/j 'd/t7vƛ:l*mTZ\d9wXЇ|4BRV_BEE->dmV =0Lz >8g/1nn_.K ]T(K I -rۻmP~̶PG$Ӄ}0d]@9$ LS;8[;;)]_5<[۹z"Jƨ3[L~F! -KE 机yYQ҃KސnapMT|SqP%mbmb4\kB+4qo}4Α?~3\ -hURa/~݀^"#dxY""Kku`xa>`{LMLl3NA)"J`ܺvMo+ErI ..xDEP1ʥM>,…+cM7~Hq›iAFWD_ulyڄ`yLq{?KaB]6.09l\4RR8W2DECWZڹ#G"qvdS&/ -endstream -endobj -77 0 obj -<< -/ColorSpace 93 0 R -/Pattern 94 0 R -/ExtGState 95 0 R -/Font 141 0 R -/ProcSet [/PDF /Text] ->> -endobj -78 0 obj -<< -/Type /Annot -/Border [0 0 0] -/H /I -/C [0 1 1] -/Rect [153.172 675.66 327.767 686.785] -/Subtype /Link -/A 142 0 R ->> -endobj -79 0 obj -<< -/Type /Annot -/Border [0 0 0] -/H /I -/C [0 1 1] -/Rect [153.172 520.243 327.767 531.368] -/Subtype /Link -/A 143 0 R ->> -endobj -80 0 obj -<< -/Length 1033 -/Filter /FlateDecode ->> -stream -xڽVKs6WC  3qt\@J5$[Q8X~z[/~xXEG9x¼ƣ!'4%$ɩ,?Y@ioꝄT/Q.X$OXC1Bczn _Feo'V:;ʪz(oYRԕNv*tt34$!qOh2gý)HfO)3³?O&^,<(˹(3moAA+zp(A; 1NrFrHn3PwWv6J+m惝n]%m"K+izHhJmNkAO0.h (XlX fzGĄ.*yqw$y@IfV1 z>ZͧJB:G+0~Ec+Ϗd\ז1x+&ո^uTY-z_I4V^V`ƍk:b6{.:o}Yu$ 76V0Џ?#OFD'm1hSuJľoDl;eSK*6E/y %7\>|۩4G1=pLJk0m\0|j*m*V:15t@<{[!3ۏ9j_=ɆS(ueY9rʭauv|cV쁳cHra3NHrRDKYhtr :(OSA'mZOPk~(!9S&|X8Ml]5Z (*UTi,߶P[*Qm?ǢlXL+g0J8~[x+IOcO]GXieMUך >8ީ8߁ UB\xv]> -endobj -82 0 obj -<< -/F1 145 0 R -/F2 146 0 R -/F3 147 0 R -/F4 148 0 R ->> -endobj -83 0 obj -<< -/Length 45655 -/Filter /DCTDecode -/Metadata 149 0 R -/ColorSpace /DeviceRGB -/Type /XObject -/Subtype /Image -/Width 975 -/Height 547 -/BitsPerComponent 8 ->> -stream -ExifII*Adobed   - - - -     #  !1AQa"q2BRbr#U3Cs$tu67S45cTd%ƒD 1!AQaq"23Rbr4B#sCS҃ ? y-ZVuVl1*fe%eR=Eesm -~6VqNk3Kn]lutNlDQUm}5EgI kE&tk^"h׌=fw<=I3sV\`M;p"gR{Mr9\Qvr#PUDEU\7b\hWEOTSqNʅENy\iY1_b5zf6Y/,FMx-qtLum0+w7/UsˊĒ,QM3{OYbbXb*8j{xIKyj6f1;*|)bZ7~f;S69$m7#z#ܨ)h&4n Ps.cȻ]R9L/cLT鯩湌w^bkol߭N\hך)\*`L i˶Ǔmfvmt6& ǂci3R^:YSi>NZEot5tkj^آk%z֤݊BQ"cIfYf/XOؑ@{c[gY[f%ox]-06YfۿN<1P*`xD7y9_[kUvG/v5ia\/4ݬܭ]qZ>?9+S}K_Sy{dK|t7mRxE%m@g]Tu1͉[9^7U.Ϟ?Dt I>.NK;&"Tŋ_|& {mfLr*|+^%mCJVa'ge&(^Vcgu46h$M2F毅Z11} *kby^-] Œi=`j-3%Lj#STG9iw:e}LxE*J5Gj`FtױqmUŨqGiKii6O -t M ]K ^uf1;!u*_wOiQtY %OnUZF^O:MGnߵ@A'ps)ןDˆk?&#/һH˺_OC}-hGC/pF_l} "]S{22L]Z:F96I"rSk]MՂjSWPW+{{ަpq&i -Zntfgl]}ܻ^ګ+*) w|^FO3RHeb91G5Sb.@λe7^\㮲jDLU"a;SuqMkӤn>^]'=Ê*bQw)ٶ\mw*[ +(l; ؽbt^2ck<%q`6 -+dI"7/K\Yѫφq^ikVd-w-PvJ ZL)n*[˂6#jb)IIGֶՌƼ|ߒ“\%5dK ot/T"q>nk=>]j7.9`eAyIQ57ueo}9~]flsޞx zI l=79LZE:4f1dsWQs#eV╊kŮjEC*)"~ɫwMRG_e|>ߍoOiR>ƾ ? jAu@rA7Owؐ,*&*"r{XPkƒA&(΀+\*~zxPkG秅i秅v@~zxPkr**bs€;X?=<(T^⁨ CR4ѝS=ʝS]F)6ۙ:In ES.n|Y#wr)C$^5z>S[.Y'&:]joX|f;ӕi=^x*!x$lJ|RQs\بeG1x`l$nFz+^&(f'ED|&R͕VƵ}O-^Zwb)muEOc3m&ώ9z Ū[ڪp"v>ώ=Z5G5Qrb&T]ʊX֠C~Ye] eaTT?ʎ/iu_Mm{cB;cWjpR?]اy&Gj`lsKpk2af-5ځPT7bѽ6>79NXX>o[IҖ(H|r5Z.V`Z+i8¶̟Q/oF۪[Vc5_9i3WK+sVA[G"ULQw*dZ-^0חrRixֶ%dVl8D'YNQ6]^cn>8q|m3M'k>1q{ĵhڪhjpIu_IY'wXKO.&zNj>Ljs]O}pM{S\ɥPs? US_֓5d@>U4 [rYu_Ij tT[raqX31 93fW:w-эʍrD-"aˎdC~L}e/KO'ru_IƓ,'wXPrչ6e}]VĸJSrS-㽺KpW*Ɠ;ӖKOU4 9`Y?RexOIrs^4[1iFõ/M-9ͅ5֙mL]hIng;R+Qhӭ5}2.ֹ;L: y"pt% oR:\+E΢so<*\w*{kCO<G<6X&j>)X湮LZlTT2Ѽ0k>F'Ϟ>+ū#pX}SgD4UKOn.)r˸ z)R]BuW+-3wNJt_Lݗ36\kx4JQWEWko5dNDMNv-MF\9TOŻN3{r`iYY<}ld[*&+bך#M -cdbʉos+v:4_#ܞ?q_.ӮhwN|#7[vv [#WS_G-^;7t#?'-=`Gvw>fcY$}2V\1IsoNiq?J";m:Cڪ⫊:ȆUޣ$Va\SJ3~ -(mIk:K-U=],5TIiceVs:4%!`w*?⩚0N 'y_|y -^¾k^Q6aڗifšL&.?΍S{$3|w v]֚p{k\dSȦn>YF慨zV -IZynr*r'W-Y[m<Y.rd%pEbjs{WǓ5q[ݤ,#'6""Mv+WDC54,,jm}>8ؕv^UԶgM&-=>R0HZ]R剛jv:\UDڱޜO>_oԣ"7OE5M=U)X湫7ÇfƓ c1G1ȨbLjL .Y6Gp5WcTs<^~'lG,OxcIzW7~{KOZ{w湧Q_e&z\9|.{~ߙ/ev=%ɶgS:*KVS#DF{ߤ`ۧ4T7yNnJOgŘkQDDD7"z(&ujj~;I;OՆ-]T+yHgpMsS\C?f:OՓ`Is~; ;ӟ31zsyr^+~]b*"]Y.}xYмGErӚxU`Zʗ=l="Z -9-݄z:59Wa.jM{Fb5y]?T@";.qˎl1YrbTs:溞LԞVGTx'kz>7rbLL>u˹ɘmԶ2F.ֻ_\env0[GsWKI~1n\I_ΧDs=O}Sn+zPߺs1t|ƬUkOi~/g wQ3&Nw\S -#w'wRD69/bgG:ڪڊZ_=D$r^)^XGp ꇐ[lɕ$ZΝUo/q*!uLBk^CvH[6p[wדѧUs\YK'ѤR[{Q-YV[**"6{3{gI}k棚T6{P]OfG`eIsWBpהMazVjF=RJ&*]mtNNMvۙ>OM.;ENZUXj*/;srtZ5z.V;GIvNNrM갪o6by~/_2h^"b1G5SbƍEZK|"XmNɵ|~3{GϏX8zNے VTTEEj)Ե&js딊vʫ,5<~Q=^Yx*W}ObG#$F|oDs1EEM襃fm̶I5rrNJV3Ƭc%fo/^<k1]Oi3Œ"/+1].Ùˊ/m4ώ/NyieݲJ-5On{6db^ޅ=jN%>ޙx5x֣)3$im\ԯ^wot]Sԫ=쟹゙=>|?*zzY=< 99Ɋ)gÝfƒ2j~;I;OՆ-YWqӼγf -`-tq*volhcU6GɓZcmm ;YY!ۛf}gS9~ۛYY9Vhu?3Gx>՚1Onof}gS9~ۛsL}*QIMom4'ĩ"O+芽Wߧ`:[y/Is~; ;ӟ31Tr3m]m*%}ѭ]#[O^to-Y%Yg'eӚmy~PȘ1W$^14YjZTTTOMrGQMrfg [KWP]G2?.N"ǥ,b#H Ϩ tޞ^;RѵȾ>urCN/@pW*a2YoR='ǐ+lƵhò~sek.4ӲJ$UUkX90UE%bs]Rm뮯c,kޝ_IZM[\!Er6TWD}yHP 6X^bzq2F*9EEMGGoazVjF=RJ&*]x;97Ing;SovKQiS-}2,K{10)xk _FL,V &]4+^^xwp@l^f)cd=$Fk**lTT2nkf.]-;1էzpM̓{yg1>.Y8:Ε_l E5]-rCamelpH$U_%vn'm;%Gzw?JG>_:\ nUH.4k⻞7yIFm-tXޣ};ՖhYS.p=m:$ksؾS]ȧ9t0ZE:]C`z јݥZDB Nŋ6| 菹bd[-Kf bJ:xWuE,quI\}sOn+i䷶=ϫ9Lբ oIsm>4<4fM|nGIQh -Rk:Lh2`:_/i'iŸ>|r8=`Gvw>fcY$Ѿhlj| *jåpTb#ۙk>ff -jPr n N; ┊r<ί 6ȝe;UԧE[G>y\Ql) jFZn۪ձu>%jE3[LN>qg̣[3}.U⯷G"RE&TS匔B<ƒ J4{]֬NU`j_pst%|Na -US5>`#@ -Ogc}-ּmy 'ȢjGkS_!F3-[cޥ\VS$NhF]Ryb!" -E===sVZ$]'LqX(k<m/ *zUGO+v#wx}-)+g`lw(j܉"Lw^ڼa^0µ?L4תTU\0Džwra;97IngOz^lK-Ңuu-•3Ba]ck\: -^-L=Wd,+˲/ 5Ks -y6 ϟޯn)c6{Uj(ʍֻm{eM -OEVŎxyLi/xZW*N}7 RnT&_nN+rRk:;]{*"(@gWՋMʨEjH6 ;N&8 ԖX}Nn$U E*/WryL^V6;Z>+ԯYz,y˷7nv5 F87NSˆu=9:ǫMmEDTSPέK5;y=ѯުŢ-5lMY;ln2G J-{q_^p6`jaf_斯ڶՋȷ+eˌ‰QnRmY jwDy GsWKI~1n\k$9?yQϙVqW\,C%V^]5I _JzJnix 4 Hw:r].SfwԨu N<;e%}hG*>gQsx3گEG7O:v[/V\.PXxoZ8EV^&8;1(Xx&(Sgm9&DtE\]ˆk+_U9=c$2+ -US5>`#@ -Ogc}-ּmulixVOYP{5qo]o zxSDDDM Uh'eD>sUJY7'9{:{6@݊U#jtJ~zH66%J합S[jp;U*r`8djf0P^w*J>^\6'.ޝ$qOut*-WZwR)]4.ۿsck\/xk0 _F\,V .jbs]Myz5Hcdވ=*.TTކTl>dfpl9Uh:NCJED/TbTn5ᑾK,^V=6.ZZ%0Q0Tz7+Vj1ETuy6ܼk׸9.&8 Ԗ^Nfʶ\muKՒ'՛ rFIo2`5'IήLNWNZ2#olO}Ao]O1Ó}> DZN^7thmD- /y5KK'Fb\-\0|ؗ㴓Xb>^Pr*JdbFQL:~Wf%)-g߯-^K?,g߯-^|]~xj?Iϱv}(~O ='>W<'-ػ>yj?w TƜ^mZz钭R#GǂbjN4k6,d;*?ӰӸ93_J9 ɦ4H⍪kSUUD&~W**nb4!L98KҪuL/CEYb OP?5,uRp3Km[1Zw{]B4q'vqw*?⩚0N 'y_|y -^¾k^Q6=i63]y$l_ MWe7g^ٕz'69EV:*mEQY/t|oֶIM_'#d;%&ֶ+ 5)g -uZbc= ]{f$ N(1ZY1_kEVZG0^\6'.ޝ$qOut]*-WZwR)]4.96ɼF@kYd˒E IVUu滕a_n+>zf#{$cd={WT]eH5SM)s$ UhjWb97/]ˋ<'V(棬tX 0s }mb\&*" -@%(ֹ-I 4L-tW7&e7IßNQϭǽ>o*$rF#r>79jSTTފMsSG9cZ9QQv**("f;am Y+CR1w]Bqb{i>Ηa >>/x]Ev72Tc:ZFLvh]^^jLZ3h֏=W4¸+{U'OR}Am'XӠpu;b_Nap|:p_Ju?#eq\?f|5dXNNq}+8&~\2)e':iZ.U;nEV5ɵM/cZ&\%WJ*ܫya_n+>zf#1F|oDsj**R#]ҘsU"mml95|VF'M˳vycצ<'Uhfi 7C<.XDVj湫P"bcXZsoq˪$I֚qx;fO^7^Օ^-|7eK*N(qETEڄLkS&+Rܶ&/iQIe3+Qɏ:c[Ɩa{5&k>DQ_ar,֬.ޔ5gq+J.8}vRO "Q]SƋT"v/rVb YCp-:91Er+F{Y&dUlu5d^{rQw^>մ:w5~Ŀu.~$G3Wu8&)׮3H'j_vk$9?yQϙVq(_v~/ԺGY`{%iˎ GNɏu\OxȲ>@9%.R%]\0D&667=LUȈlF쭭?To"'[Z~߆?s9$+kO0|x|}g$emiS~쭭?To;cZw.UӪID,2XFsWEEܨN<6,}d4WH["څ?Uy㹲Ehw*?⩚0N Ѫ9G o9:iMDqtoz2Gm|tFi)Ufox>:7=WIGx>:7=WIGx>:7=WIGx>:7=WIGx>:7=WIGx>:7=WIGxH/:X+GnrWmބLܶ5y3B*0UN•Xvs YMjexJ%^챮\ĝO|%SyЕMB }R.S/1D閶)T[p<⋹P{yO9LpcEzy9%rȧF|qhV_M.XjL=Gcʴ.^zvmӶ;a]o4 (ꖖsm-wprڻSN)PZnv{ENZWpO96ɱPF@cX×sGXhܻWw7͟?_ֳ s^sU&-r.(ʑÙ}Øao]ʶ561$O%ٺ>l<ݱozKmH' 98^ǵpVrDưGs`M*UfDzwC2Mguzn= 3/g.'e["&Wo3$[쯂te0 =ǵ&j(I%Β[kijgU^H6Xݧg;]mc{X5qWZ/*k"Eտ~W~S}NᢚFX᭍O3q^dԋn;Yb[q= -Zkܐ 5oz.J* i*#^g#~MSY䈽gzodR==5.9kƓ ^bk"mJ.,JI3xզچ7զ]iR]($XkhfT"r=CJEbY܇h~P*<; HqN!YopW*a2^oU/ͻӸKu8%4{+s9*(orc]cr(k"d_#+>ze={cErR#}V:L e.b#SSts*'K7.3a+^ԧ ۶2VQUGY ܱO*pNEOAh'X s[b↢'p^T]f'Nכ-Z5OZ{Uݕ6(sjpJO9~pqe}]&=j=cErM@laC3-[t50|ؗ㴓Xb>@OĈz?nW=:ٿt3cY8nd;*?ӰӸ93_J9 zgg$Tyɑɑ/8YмGCrӚxU`-9vѵ7p33m^s1lF9EEMCEOEKEKK!&lq֧qffgYHs.?i\v[?B"ѧ&l3|/6aBΪ%2YS@.9NF5rQQwZN}[ n9Z]OfG`yT6jN_-`%f|մsT0l} "{Sv< ڼ8+y镒J#jUk)q'ps[y%;J:G#)΄R&Hml⬵JHdsog'N).vjˊ/V#g~h#tR`z27ɱy -2bN ml Utߝ4*[#(kU6973a7/}9QI{]Uj[nTDTT6*_h8: 0]:˹euʌz"č -/w͟?_ֲ{^sU"+\*.E2aI۱[G}RעbDȞ3=s.uǨ[ƞbX3ka769ȨWڳY՝a0LpC՜͔\f; 2/Z;$ڱ6ۏ4́v6NLLu<5lDY`ʈ7&$[laz;<{1 KVr1ij&ҬTѹb9H+ݷV2boAE}~ƮI>zm7 !/_짬V|bIYW|V͘ e=zO:G;7bmR{/6V4Cz -'=g m.REQIMom4xT'pQW[t1Vb-w짬V|'y짬V|bIzYkՇW_SnJzJisjUH㙮rqmחf"}~bzj LEO5i5Ψ5k%|/TTUЇA)Z짬V|6cO[M?~9%eWmwY[;*"OLpYsۋWMywoYي.FcsQ_(*mol\嵶7K4lL^9EٵQQqw짬V|:O1x<}oACL^>a'OY?t}oACL^>a'OY?t}oACL^>a'OY?t}oACL^>a'OY?t}oACL^>a'OY?tz o;MWY$|qIU5U7)I2MecHHWP43)fD^i;<;t;sjq[W -ߝ5dʮK׆ NdG{<<4llBX.ݬNőQd]bLZh^mXi+ShδGPPz'ZZu]o;w͂iVf4$2;C]*gkrM -0RRScc6V5^Wjr?Ҫ7;eqܩK_J -wjEEkk\ /hk` 1ػ4WYcUQErrEy;ϛޯY6j9LQSj*/)+ιÛkEt YVǂMcOԭ-}1*'\kI*Gq9^(W5vg lzzi|03drrP11Ol%ܓ tHT.R=Icuѫn~w'ff -$V[O_%^Kl:^4zjZJ#A+Q{Wv(bf83oWDJ51t9]_F^n[A3=5D1> - ZMs]bS䦩gS$'+$6Xf{[/eb/e6bD᧝0dUjnldϷ -~^H Utݝ4*[+WЫp͇ڜqOpj-M_HkSbEMZ5@*" z1Ƴ2e]grv*lRKwWo6|xAkj( ][vrvOTTچ&"cI{ǒԷ5gIuԍaI.6DCM3zQ6󓬜,&lzroO"]ʄu[.;UckmrUt9XNgaл pxɎ-_p)s]3rܨNy ܾ{Ěn|T{ێtO2חs/k⬍uW6ryu*o{2Up|mHׇRm%"|ŬjW1beL9?Y ?zfɊxOMSkq#RzP. Y#t5Q9],Z>'۽j4ڧp-], #B>#,p^g ;.ecY])w5"dfcG$rF|nLZ**t*f[ ]&9ڟx4XZpbHj{S݋4̫9"fLr> W+iDmOdǒ/Hk | Q\5rTsTs\M !ޗG9dm΂DmVy'}~o^ -OzY jƓgZITTT563a{W3kS*qW+iK]J΢DwEMմZ5d*`ScYeto2NR;}EiUv$r%;ϛޯYj9LQSj*)+P"EU -eغHšݭ[jwQH0D˫[z|+w\$]$c69cjNeJVuH`太e]RG9{;jыV-Lk 3+zfZ2 LRbʸCRڜ﷾oǵQدOr~KgYr&W=]3^MsVTfl}ݮkEjEMʆxPg'չP̋αqB\:xLaNY6&Drs.=KiK%ڮܩ3ƪ^a(bk.y+"S_))Q=tX!]c3_ w&|ӭjn2`GpDs2\V'|,z -7.>9ls G"*.(QSr ~MlYC:a$2&)#rmCm5a궚ΰlnvՒᖱgTjTUE<_ʆմLk -(2_Zjtg3NkUH)m?#a>Fܻ7͟7^=𱨨.S*`zѬJ]V-wcM90E-Tɇ}O3̤:K.Tؾy c^}Hwjtm, O͌o5 AmK]ӫxSR9;&~ݷl9pL4~+BX9y)kGƭs՜< s+zvΉ#{kDۗ#Ws"f3E));48'mn>>y;Z:Mob|8"j#oXk4Lgw?݋4y{1NS3KhȨz(z挢[%/2;ewO!BwiZ2kuV2HXf993a;r4i505CqɔF\_jSInl|;-3NpW5EEVj\"pTT]Оh9)*ij"TlO9Tܨ&5bcUU:VYcjzt VnmDiO%{^|CN,iַ*GmH:3^kܻ7͟7^=𱨨.T2H7G#QcQQwf'D],J~.X*W~ضpcNx}XN!6i H+Zgsu:Ǣ/6Yc1MxJes(o7Dk -+'sw/miRh絩f#Qk*s:5 8SaYV,Ʀ.r59p131<eFU )Yjecr"2**7` |\NW:'YB.9*擤S[Z;խ%t>GKY5JlMy>kzfw8IUZ]=|zQO"`9?6*_V1pp@*" -@&}+fi"_6 G#ɹvn+7=}C*p4{#ǵ"z*("tFAm_QokU| <&<;nePiѼZ?(ҨdTM;FS|6{xrτX?5psWb*.RG9rD y}l9@jVGc]+wfk[M`j.QgkfUwDz5ċb];Ժy1cM%c.~c˙3[=e{Sȭc˥sZ⚻m{Itw-3JiAYW²X+-fC"tnsS/-_b췞M/!iΫWfj̼u{#ˮɓt\]r9|1WO"T:\n$HiK"5y2E+6mokkNs.p;jMG+\{iEgYʼɱ=u,ۋrXm>N[Z8#ͯe,CG1uDi/>?<~ӹ^_'-O/?ɞsRz%Z$Q*$v<>Tkr]<'cw6|剞OzoZ`?kWccsT^%}&,W5.JVU\G*69pNWs9&-4cMVkw+=9)'^jk&VqŽT^e6;>Y׹k<&;4˚U+ʚ(۾^4| -DIsc}{Ѥ5Njsrۢ5VmB51TV2"mٱKΗ~w>m>_ԑ(tʶ{{zYEKoD\cyToMgHWeG>y΅%oCd1ܵQ.Sq#=5Ҵ< ?bŷUF+Gq' G7,f)v9g#E˥TTWYmQ@ώ'tz+u|*XVo9rG{쟹8sQTs\ɵ%8 c7{#ĚWvTtqȩ+ZTF4ZtmnMtv|#=Ajw{#}Esq|Q;r.ֱNOsf>=>3{m?ϥM>5nH*q7#s^؏i7Yѻ.6n5ptTSnǵbr9f9vu<-}f;Xt-kij>Oˍ%O_FҚ$ON^;Y"G |qد4ijΒn˖ynUC |yd_ǟCNu\4Y#tyι5z4rM2AkDç9U;!qm#_4zNeNiq>6M3bݎcY> -?xG^F}f|+=pDƼl39uyѶsDDL.Oyo0 |n{v9E;⹩g,[: -C+&tʒ,:5r#QS+^Y}%ǖ9[iX=/Z.V=;&.U\ %[Lw>L{)XrGŜ&+zq g_k~\ѨUdJ{%_zP6:GVqןG|@ ~'K[7IŷANOֶ;yguc_Wce]P2cTJOIN^D~ -mrbw -O:rFExms62v&59S{tΧ|qνۊ~ϳYp昞|ogft))v"&$o}Wumiξ:SGԅdYµlw)qt0cE݋['tg&unm>v[DLqjc֕'Wbxs=6O^FVnyRbT1Sf5LbI/{ӛJμ-]5Rvmv'g+WrDZڼu=sӚ=/uN}^Kv8ݾk_'gMNWʻƧ+ݟ5qRmn_q15PEPhZ'MMO3>.γן8Rϸ-uD}-hkpDGzQ3F j6J$~莑O }GVt8h|WN-^2C#1ڬUU혧nw;[i:ǒxf̻PU&Pt˅*voE੦rM^tEC랜sug|')^-\yݭ =ƫj&lGĬj5ZǵLſJ{!2LX -**Ⱚl|F=|]˳qWkӏ|,B.;Lus*9[lOT+#Ntn0{nS[k#W͗.36Q' &0^xJ\+czMmvݦjz%%(>QȒ\޶E۴\6战Mq|Z==LX8\qĸOJ$bH+ ߢǤ#q93Amb9zYhUZ5Qo]%?7;enm^ݯc#cXƣFL6""!iγŤ,nFz+^"+\Lz($'XoL̹g:IQT8lV5ʽV{W-1^a-nGo/a<ӯY^rg;o3j~7[w>k6pqwiF=2WK:'+!DEWUuR)߃?m|<9S,X,h9ȨV7ȪDqkk>8m>Ē^9& bf4B)Q:/FQ6U:?z#Ϲ? ;=tK4m,1TL_`;>l5'OD~l>Wj^Ԃɽ2{ȪQ8LJ7R~3A^q6$FůWw8y9u -:}+:_$b`v6M SDebIVIkEEފbkOk110.SΕ*geU -#\$cqNuva7Hko? } \u4q.&JڎOwZ;|Ƴ1+gZUP5{7fcMmγz3|K5lOI{[%l`%rbWSQXp_[uיxVđinY&erZE}#D39#^F-m-فd -T"*2Ir[T>{mm͖ҳ4w7e+;E^T|ɑ`ĘaEb>KO-O:f&_HӖ#Ж=9ڎE;E!4ٚk`uc{kWn87nnSqhΒ]{&ΓHZ:<̹dKꢅtrH+pM&$Ӓ']zwY,+3""ԕy;j;{lM^Nds -]6_[OiNOd?=Yej{FZH+iT6'm4Dn -%HF+o4++uoǓctC1MH_w +{Փj{+'si#r. -&qyZ|WIKkk(ܘg~<FHɒonzu-_6pSVRKUfbzb5SE<ޑhؗYmjΖ -TwzBciE˽"݂-vێZ5l#l\XG5yS;cWYUP57pFto"$kת,q1}_` 5<{}I+,eұj,"mUNܼm\4C}}inHHBWZ˔|R# Y'DDE^|1fּ-[g{ӭ/ؘt-&[*wc+UйcUt;7YN6чwz uOj=[ܒ\(,wN]#Y>娫)zyjh&/WMܧu ELmҽzO\VxtJoS^o3{NkyYksO1ԸS^8>Wχi~}~FOx3MTp{ůF=*tm<+aZ~v%#d*tGG <^ћq^Чue5T'ӏDvn4nJ>?_ZҞ?G8=؜oI&Tz?BMt<;.뿡KGOsO?(IHHc.o96M~WwQmr;uZy|?&߽V??=O/3?j.jk<,T`=Tn1=aXҶpڎ*7|;O~<ɏoGB_V_ҿ[R'nDzv?Ntt?qpAQ&_;($u ]S㳔m䉜z gYkuga/ߚ?Y?ogUi^,MBL>JقbMswS:kkv?"GOeG ɄA$Lqb"NYDO!=Ţ-1m>oUUo ?<ſ,>B ~e_U3=.߃/-ѿJoOE,z/_R3N/㺬p]o:¯Y2.2xot?)zƿXǵX89;ȟG4_ג*MSo햆hn -c{7&-i1cbk,3qz}2Z9y{}՟_~m_g.@ՅEEJEةڋ?oytgd8VlHiWM"Kjk]LϏ5mh,{Q]fޔvvO&|g49ڋth"5,S -I0]z ;}εGso0! &v6dy6f8[ؓL,g'R4K\$UQªpɇ;˴՟ rkn6di͙rk5[1tk"l|ONG1?>cv'G2F:9cZ91EEب?Z>%H&ԥVBo4+MnxxBB@LSz2EE2-Jܻx\iGm*8ygX*.Y8JK#Gtyʍne6dfNP̩{7is*YGYCY=lzQO"`=7b^ƱEDTTTbSJҭ>V*VWc`1r(UovZT6Y/YRo1HӞHp"o|6qe7q$1=i"wgETŦrEk^NKco{K{m5mWƱ{9"+\*.TS򙉉G{b+.UUTL$D -ߨْ<ÚJHQXbmjYüpOsk^=v[vZ}y? %ٟflBzw:|ߪqH3Ek9>:d))+c蕖9cbtw+y\'t=[$U*~ZtGI`ڹiy^zȞtj hdRnt_Ml-s1ϟ6Y̕Eo[XEm"$ߺtVx&jM[Nu157^<;芅R9pZ##зqwK[!(4<ʞĂY~8s&8&'3Q.nuNoƷXdTT6S|UDEU\6[i.mq'QT6dbK&F' N-(suȮI+v_|Q|䙍tӄy{|\.1ҳG5EDžʛS\ m:v´f˯7~wM}ot~ 4wEzgC௞]ѿc;6oۦ$t/Oˏ␋.mNnVGoVL}?ߖkgO1M]'M4d9W2Rf+$JdFv,HE#v9Tjv^}Gamin8OxrJ -mA-l9XkU9ULyNOboYeIg|W+1˯iUZډ>g;:1Ķ?_P}Uk%&ieLJ{-a8߃¥7]5o'=Rf{Y'6$īV7dr'Bgt;ˊ+{}>7xy'X6-\M!c"m]¡ض_)=ʈ9f+zQG}yz-33J|=|`͔3CE5'dV&81ȻpM{n"f"cF-bmXٿ?/Э௞ѿO[7a#~/ˏKMt-W@ձ4g1z]ѿ_=2DRˢyR}Qm0u;ZY1U5WUrGbN;1<#ܿ%>)rg>gj\kl_;<TQN^r}]5i ~m‘ޯKu %L1椒tszG ૆QƺVcF-?cK,](o,j514I#Us1MU辐ű GxG9#kؤqӪ7N軸>IzW=:ru>)r31 - {k#:$_6sѹW &L쯹~'uF4^eidNzۍ50oy]곸,WH]uo''4G.s IccvTDNꨈk^Wk7^([&lW5z41hsr911ŗ!UKMWM--TLfsC#Q{+\ N*εhoYV'`sx-rS)u_qײP**bQw)lJ:B[nJzTV)|G`wR|k}MYi.!"7%hIk#Xvl]ڋȧk:m5aLId=VXh*0IzM^[VEV:{l3%IJ&hX&eO9)%%"Ѥo).Rj*؛4'ܼʋSf-Y]mIMh)%+0U겡~# M'UJ:!}5]3:yS{wa}Q1EبԴOMF4VF;cvP酄2KOUSW5/F|ʯbUz]}"m3|^;~%iXŞx|6-`<(jh(1q5岵pl)7wY$n7Mo>¿45̑Q.;6&>d7o$~F38Ҋ:oYT}w$Sgr57=3M&/t9rVp?|W:'#JrP|u{_B;2|z>Tt kY $s,9V/[ë1Nr<եr\ߨ|1٥[MY^& -ũmst{7דTs7%s&Ҫv:sckSUFWuvm6}Ӆ+{f.Yb3idwaȇaW "|Ө9/z=ZGVs]i,s*SmLvQ|1.33|_gOid_XmryqC]E+"궖.$fIZU}1lS5{<~*f~ȴNdWRc䉩wW}?ϒ?&1tݎ4YMM d7ZP"$8jn.zgIOǺ])8p|3[cqj嚬3hO= Ŧ+:sOwn:Dޱ?/cN\h+-RO"!*"6 -.]kҼ3.ԥs+ӽe&Q*y)ZɜFpr"o贵p1B/Yi}Mf-ugĵ"EѤR6 ''ruUoo )]vgeuc\v*Lk^GUnv>XA"'::7't7;[vk_W͖4ha]_ΏJȑ˳l7~_鍌Nu;:fLɑ&TZb9FΪ;~ L;;:s6|5JyO;=%KkEJzV#˽^.;,c"2-_ⴺ+%Kmf-x]W~C^k\wPɴ)@|*9*:.%bvVlkҶOi˭{x?<9Us(aCi7sޯc6:NIjdƢZDÝ8t̍6um6T剎ҟ2^x-(Iw#ݵwKvzRϚo圗?tx0Mq6jd"xQp*:;^剞.ԥs+ӽ%]&P|UPIO/V9Q.D\ &4TQ5rG ŝ"(shS>i }5LvIU[ؕvF㳙7Ih|Qxx>O-]🹌QgcbQx[p$z;V!Csc2my敝{:kNy>f5U]ƾ#k[ŷ;ǻ3e{gNtz^ohոiĘ+زaf/z_JSc=~Nyi,r*-YpzV<\Xaq8זtms4"Ɲ &nUu7ȍ -.^{ybgG>:S'=^ST&d%V9Zr"ȪW")a)51*rVMf-Wos"XTqΘ*5ȸ^G5v7> -%Uo2R}4@y ܯ\4B*k;\F~Nt{k]f;?]gk,D[얌L=ҊdJ#b4{v3i˭kµye;C,젦^W/iURSwB&"~i\KS_wonvc-S+O:`&ֽ6\ۄy6#%'KG.bۖ+&D*k;ȯk:w?'Iznmc[Sz5[dzዱMW RCO11szVwciדpClϙҵ=5M:xwઍb{b۬{/l)1GmSvD4yV޺QXa֧# 65H{:9cJNTf d˶׺-錵 sQ7ڧxyk$ʲ}Yh]2Zv.uLI*dKq'^yUχsTBɘ,׫eU\qGwtZ|12Cf;ۥ2Ok[%,B*mX.d_Z,c:lV#bcy""2N/k9,gK:]rZT@'I=Ѯֻ^LP͂'V@EELQv*/1T\՗`N+e_16BޞJ.Γodr_i8k(TnG5bQrr[XiKt.N{oXlJU+Z^6v/j6;'֪ë.crxe䷑^KyjZU;DL.k hV: Mځ72uL)+.rdqKI8!kЇŬsBǕ &p[MiRU,nf_'v:O«UTtuSQBjgU' k&&5 ***bQC)E5Ϧʹuu;"]%\U_'^VgOL,@6(L$c^D_bb'1i#4F'3Qgq!-xbҜ|о s/\˙잏]ONq:&9rIXR%Ɂ ) עnG"/11Ř`ƣDGm3Ÿ Jjf"b;ycr[\8g.$x=p\$1Ihƿ HLMbxE8KVF7FFLjeS+&++S,x=p\8MNq:&9rO3Xz;Ɂ<\唭.V.V3>wZcTS9Qdv~2Lw-YoRKKSfhzpEފja,]1!u}-wb(_Ny㳝tfwGj^; c7=]^mw%ѸZiT"v7׿<^sQTs\TSCj~ӄb["Xq1=u=yyLBv/gЁ oxiXji`#NэKM>q4W`ڕNƾL{$oq|f*q[EF\|ѓFT‘nVe6efJVTC;7is*WIWGW5d/zQO*pn /bcX*#QS]N+K -lQKV| z*-}za` K^??+_7/C'ǩ i?g='z}r~;'l2F9/$(ȳ5}w`ԕ;~U]v3jRD* 8_^1]ʊOC`Z3w?fEKNcšF*YVUu97}}Ot9/Y;e*%F Tmczty.q^-3mmemEtKm$od C%2%e*#QvTgfM8lZUآvO_uu,/,ݨ+[j:=-C1kTC1:vj&Iə'?mw)]s-d箫|Y966(z8mwl+ʴ 65um>j XV+Hi}2mH|LyC7.qΓrjZ_OUND{݊"T0a4 -Ç[VC9S;2\jIx%)0v꫷# L'}>tcE^ƌ5DDL0Nd2hZH͙f"10ؘ؞/]?}[~OΊEEܨt nj93ҽ|NG|-k.V.*cm{/7*.SBj -Y[:K=,|]& */'3]Njai_,#z@q9PR6W*ľuZǭuZbZW7S:jk)-;0k'M˳q;is>\'SbD{r.C ؘjNp%щ ./QMސ~ykz58⍱dlDkDkZ""nD2n[Z^srigÊxn L62^i9w;,]BqR6녲QnSIG_J -YE^EMt5Z5iu̎Jj*()еWc} :Gp3b}ÌhQe˩6Ex)i:0Nuk>3 0k7EOJNi]=qZ("$잿 q|mT+ƒʈ Nd&"\ܑnF9mS(:.Rl7S_"f'3z޽ -l›%9m0G];6v 6ʶ56G'3yvn'm7ݷaQΝc9SsNH1Ovo`Zֵ֢5L؈ȆU @ S+j6wW|/d㽎`I[]gWU=1li$r+fDvGGm6uLgk0X 򙆦, ,:KUA5Ke KUN{jyTY)biGn{bEs]#=9zUwKsZu"4{@A &LᶖO)YG#$F|oDsQQST)h׻ {:Gc͋g'7[pJt@XT7;sSqԋOZOJ>~(m8|oEkػQZHY~X[TҪׅ~8X;V%QQvTff[۝+vmJA1WFRsN[ʫqN[?44b9F[ uG额}Ua^$k͊J>MuV\6e{ࡎL[G=S2pI1u<7> -stream -x pWǟp}m"7bdFB3HhhDADD Eh1kUhERUC)}K{w/Z!sN"9?'9=`Z M(&zk͚ԫ<:C~JӤIt6_fͥ9s~}Z -MtoݻG/ӠAEjjӆ L@'b2Z(:^@xypSFe }:{sg:zdZkjڔ An.Y,`իG| ׼M95B#پ捵ǎ HgΠ)A`zmBT,__=Pԩ@y^H!!{c5Bط<=)2C@`#j%KxkBw{<sq/@CپNQnԳ'?}G_~Ik@L[Q!r ׼u-h.>k‘#Caat<=5khZ2e@xy_L ƍ ׼Dtbݻy6f ]}/Zhofg?qk^Qãl&}kv~`6oMhD~a_es>\Ԯ\\+W(CWͥӧ9G^U:FmƷ7޺7/Uu:<8m/# ݻ< kؐ>_5;G}rfǑ#h*7rݹ6~M_cegעEN jR޺EќB};j2?NhgYEqIHCHz9n*|Kh jӆ_ulD//6_5)-+(-FF -W׉3Y E3@ݻ#ꀑB};!j2Ъ؂|ؒ%H7np} z, =3"Ƌ]Ӈfo)t_)jU0u*o*w+ ($sj2j=<(2RPj;S,Y:1%.)tQ&h2֞lMW[,j^0{ -4lC]; -:gٴիiZNlX*]+_ܽKɜBq#|LΟ Ç+Bs'P;f)t0R- ڥW{-jsrxmڔm@qܙSe7m'>rea|Rfܕ++Ф|¨ukڿM*Xوx+ȵb'ԦoI -\ܹ30ggڼMbgɅn1 -c)tt&|: j*;xqqWϢ >>N|_& G/OVY0ꁏ>')ӕ˖ -ėIM h -4] >>t |T{R˖ ~ -W #nR˝1 -<+(mwq$ -kO=F'+СZiVNchz`6ӼyJKkhP߽PüLy3YȕU[:|a|TܽK))Ba|LΝ`)S+(n,ڵ_9|uŋy++_R+TPL {B -|'8OW  *ZŖ-Lb"=_1 o/RqpМ9ȦT)QTёG֤$zMW %y5(ΥZW Fʚ6mWW xNx8ǂ@jkLwWP( WPL^2k|V(RU=Ӧq LQ՘j6Sx8edP*Ǡ%AQTmrڣ1ATJ^X 4VHHL++а1Ui|z.ѼeusmUBANNy -_ANTb~* -c嵪ƍW9.WsJ֔7 c*U 3ժGD 䵪- z< -@J<a".+Wz<-\H"~FdIrvRO|]#N5cC0+xT5!%kŊ2F -mz{{bX-E5krZXjߥPy+4X"L\ڥ-ZZU}J -護hȐ;_AAjر|umծ]y[5?j}~}>ҥ|CSHV>F Dj6jL\־9U|+(jh< - h[(Uh$+(hd>}xX-d5 - -ZV77~];;Lߵ,8X}0MUL,S5jBU9;m3W-zDu갩={{K@饪U)d5 -9G|V[#h *{re>VFV_EղeU*ƶj@_X%&Y '̫dU0s?=F_ccBÇyjج^VU FcW٥VhY|q_?;jF{Wٲ6mjh *(_/*|=Xh~ .&jbuFh*=L|:+z@cW٥RS0Y1lUgΤUUv7V"Y J_RU4q"?HVr1U{ AWٲ__t<|UHVKUU *TS:vӪHV҇Zx[W/1|]yՔ)4v,o*hԬÃU_+UaaN*T(1jUO/?C/W x[_c$Sd$1UCr `J6*~||xiSt|P:-X@AA* W#Ymd?j@ƅU| -Հh_d5 W#Ym2j@FZL _Xd5 W#YK>а!:duyU{wt쫑FHV}5  Y UU[ZUhw W#Y8쫑V!Y 8з/BkɒM:`_\95߭F뫑*HVr}5^OmU)VkՐDj$MƯV)kqx^U:U==y[U+4**V BCp@Fy:aM˫V  Dj21l -j$edЈHV}5Ն D U 5j }Qx_!WW$M|@[j9% -endstream -endobj -85 0 obj -<< -/S /URI -/URI (https://hal.archives-ouvertes.fr/hal-03518757) ->> -endobj -86 0 obj -<< -/S /URI -/URI (https://hal.archives-ouvertes.fr) ->> -endobj -87 0 obj -<< -/Kids [153 0 R 154 0 R 155 0 R 156 0 R 157 0 R 158 0 R] -/Limits [(Doc-Start) (section.1)] ->> -endobj -88 0 obj -<< -/Kids [159 0 R 160 0 R 161 0 R] -/Limits [(section.2) (table.5)] ->> -endobj -89 0 obj -<< -/S /GoTo -/D (section.1) ->> -endobj -90 0 obj -<< -/Title (Solving a System of Polynomial Equations) -/A 162 0 R -/Parent 5 0 R -/Prev 24 0 R -/Next 163 0 R -/First 164 0 R -/Last 165 0 R -/Count -2 ->> -endobj -91 0 obj -<< -/S /GoTo -/D (appendix.A) ->> -endobj -92 0 obj -<< -/Title (Attacks Against Round-Reduced Rescue Prime) -/A 166 0 R -/Parent 5 0 R -/Prev 167 0 R -/Next 25 0 R ->> -endobj -93 0 obj -<< -/pgfprgb [/Pattern /DeviceRGB] ->> -endobj -94 0 obj -<< ->> -endobj -95 0 obj -<< ->> -endobj -96 0 obj -<< -/F38 168 0 R -/F66 169 0 R -/F72 170 0 R -/F79 171 0 R -/F75 172 0 R -/F93 173 0 R -/F110 174 0 R -/F117 175 0 R -/F29 176 0 R -/F130 177 0 R -/F142 178 0 R ->> -endobj -97 0 obj -<< -/Length 24730 -/Type /XObject -/Subtype /Image -/Width 659 -/Height 215 -/BitsPerComponent 8 -/ColorSpace /DeviceRGB -/SMask 179 0 R -/Filter /FlateDecode ->> -stream -x\TLJsm׵]]ؽڀ--Htw9t'7.~~{={o{*)2** + -s +b!@ D ,}/U~k{Cbvs­g~e755PPPPn(PPPn(n(n(n(n(n7(n(n(n7Pn77777 -777 - - - - - - -  - - -        Ҿ?r'%&s凎xl?>+񄂒:9OY`Htx'Ufo:m纏R.lKY|>#Ozw,Nldڏ 7"4:*1p;ǖ1 /k^!;kf#\wƾ@58gu#{e(_W(4k7y\`gcIjb㉓]68Yi ˩j;IBy\hu]e]l[SC0#`Ef:d&xQYkɃRg&ٸ^0>ouoJn͋86җSB -Q̋J:,u>TqkZ0VE~iB2+[cZwVN8I>txͩ'cX2asdb΍gTSt0 *i![LFphZ2`Kf2669$^w}S*),?tI<,Os,|/#3zL6Y԰Ƴ~Y,í(lg0w d,{p_|q/^}!مu"\nz$'IG LN8$VOɞ^fj'M _e7RXؠr/>{b -޸ܻ q_!q A\=Чdqu -\4RY,CN)U$6KʿW@ENd3hO,#-'0!6V2%mMP,t۱.88LKr|?UEuK׼6q-]WJdg^'׮:e#" -Y~ȿ0omUM;-m#V즦䄷VUd\= .`p\6р9lmjKM1TZI#+kI0fSS5:N>MMrpoQVZND+ W[.Nȍ5½OCA,Wtgȥvb+h e "*zӻw@Ui ߊ -2=xeB y{]T}yd䊜/?swW9vvmqhT2k@( RLd.=O q욲,*@,7aqUzT܀S1hr2r0&]2)*ϫ{y*Ow8 B"ͤm[P4bl< = -)s96n((8gz{*xs/q6vojqv+A< dH.|+;xD~H%E+6!ta\]wq(W r9c$GV_&c`>^@@{lWZy(z"Zeܖ![[l_x`}ïoJEyI{pk9԰ l(bk]wG=6YΨc咋U3rzCl>ݛ75'~aJ΋:$ -=md1ͯ>=|wG0WijCj4#5V%"^EOߩjjڰ^[n …VZG"Q!uHAp&^0P N[:'Z=閏t^ Ve!@ѳ}"E4"֙מfj2#LCD׫ɮ:779Ll~V|T.TF"ȤH*)/-mku{po!F}9<={E—j&"ydc:^J|̚wf#Ep>/D2 qal=Z]mgso?nݧade3]yy#gr^SF`/ c%W˨g7֢˶W7GnP&7е~b>l!VgX]`/790%85eQ=z.2fXqZ;:Լ|K/B"sT:?pD_uB"mAa+s]=ڃQ`<S*:2m=^K覫ލ $12[v`nZ%o*+.*$J#Q^J3gz;;s>nا|Uim_cApk^xTC %o5pCT^0"bۘY|Ԛw.Ǥc5OY%񄏃{ϤSc>n;\S*G7.O4u@V-ͤZmbX -K5'#0S(U0s{5#c)shoX)N['Vtbolv=C>Gh?sAgkLM0 -dvnBw -$qʖF3MYo|~CyƂeFwDWxo8LJ@t ߘu90ަ!N!p>/#cbZ[&<9+.9c^p zF)eD\yq fv)-xTTo: p`Z Ps:49t"?4T~ίD0}658qU p<~ө4|h[Ce 禷3z3הfm7p@j5ȩv҃W|]p#mMijbx`':|"m{%;2<ΕwMmL '3%CL}ٗ{aT]ܱ&yIN`q77e4w XW GuIF`*9}%|o me0 n"r#:5PJ( -$Elk=_ XV2j*UF}ӵnyup﹗}]^p{Ѫ ijvrCܒ/*B*9H$D"yp)wV) -SnTL/7RRz|'7K8<^Mm]t:)UxC9{xU6O#20yjJS/15UK04i%9Ho| 72ǠQ0o3K*ɱ!Vf$rz\^Q aۼYNdS)$IX6m%iz1Hmm@v۶$]|>,H ~69epx|lec`w&B_uHGLFhy.m³,ZxD17qE *Fr :fd|21&]T+msVz"9utL@U>2oZO"]ZM  lE._.X{_6 ܊2喉.4<J+ ?,Tbʑ#J <gm,F!;:&&'ĉDY<2'h_%8i5d#$dbsʪb#d~Aj'}9&^d&q? pyqaׄӱ+|nRVqLvy\ .#HdbEA)5$vMimr2d`f`JHi-/TVP`SRZMwvA\.1ģ quI̫sx .:Fu&tW둬Iʫ|Q@sYȹȣStc̣lbV;ͭDfRRWQuF^Xt*9>XPtxݽֈg?'(MDBzrAM\޳$ķX/uxa-46 uzN8~CF 5{a?MS0vc!)-}vJf{y>>P[^Fw5<Dk 1:޲\.!ē+#6(SIu6y:Fn8$oZ4Evݱqӵ:&j'JnجEvcesl+̠~ y޷yӷ]jlAGE>cm}Nm5iGY3>C59=]= vt+lwl^yX₅aj~?qU;ڪ]%ty{!5v@eUM׿;|Z[1]๨^xzPT}`۶m]5uH|֨͜13l"IiY'>xZH#FA-w3S;T>H!Z}e737wKrι.k5{ùӹ j&ӗt }sZ5|-| %BO{+jWKp18 bw7*HPǵUD=?\{YLat_4TMN&eoc2:'QCw*.: 4kk?ל !rLIcaUAj.6A Y5<3)ݑkPbDaŸK?o f]<AwW{*8* S'`H(J-7RʒJ޽|6 nwGN#?O7@wW{*Ե2VnS{t;{ZEn(P~r(?+P|6Q ~UTVdcc3}t(hGm0=An An p 7 pA 7 pAP!nAn7An77 p 7 pA7 |>j pܠxs<1p5kShrTO 8 Lx;yHKUTԕ`vɤgp3S뀛âEbÑpQ91iTO n ㅢjJ79yEV) *#;(|\ڷ|m^a!!&ZU!U-Ҝz Y-}kz"*'7" #&ۆ݇,u\6~lUEiU8VgEʈǏ=I$1x*Lsw-r1ui?}> lpkzIKE{|>vpXĤsS{GSl|>YfX\IgR*z2VF!E.EϞܷspZ=PAf, -2BYQ9,Ӱd\ Muq4F$ˍz$9K*I ZX^/p-o`6vuۊz$`Rf+j]y#1lY G VOЭ'P"p<@2vP׉u`. Z"ɩr}3n]{Fau$U'WQ>UOcOPVR'Tt*[ݽߨsؾ Ii;a/~Qܘ<{X؍Tv݌n2݁Bu+Tz "1NKJC -kLuÖl5:ᒂJ* :z`N: -uMyxLJp.`r?wߊVTX%7Sn+:\`?eI2ݕg*=pB0 l nok{vm]z:[UZ6~*'KJ_A}˩7 -ݨEm3v.ߤ}n\->Paݮ& ,uâqr -ݖmϭ|Xcȹ IQ(yddKU -.Ho -O}>Np羾R ,K6Ą>gpIza8$ -)bd0);tn&eֽ{&RiVP'/rB|`Qa#|ʢBoK_Sl1jcu2ٖ6Yͣ.t+ZظMnqhOL Bpa\./lDB/*( yĒԗ(O ʛh3tKyk >1y;^U[& -.d8ܜW+NNi]Snm}}䯽H!&{ʅ܈HP5M %fXbIFō-tr=/guoia:1>)_Tg{o,0 Gh *i%<==F8f>ܸd6 fΚ/|kSakCr2 ܍17ly7r$Ljsq,r0ԹuK32u}TcL]gx]8b2b({k###FK< 27ޑ.a6Oxzq'9ҝn[V3etky{yŘXʤ24uݽn^ot5KIUcGc-QVoX8 (T|\KEYBK?6=sg5aVbo䤿 oB75ڜXW -Ed$\ŭC ;zg2|ɪoXSjʒ.أ#ߚ67_٢iZ9k?Q}C \p -g+w\rk֨V]TNt&#! ,')*n9|)͙ wQj~Zc؄Wd96}9l(hyn^7o׬ orpi&n=2E\3S(<5GO3ޝlc7=q=uYpk$N+oՑYȭYZ;̎X,Xڋpi9-!Fthd$-Xzo?{=N}ϡrڷdL(fni7rtC%S`TjG;BI=,$P-oD߹}󂊶ydsC_#ĄPߩGA%8 7-̈J,"DH̦پwӒRU $MO1}ay>Y>6=z ܰMOicObfNbNDNqيz|gpөDW+'A L e)T`RdKh` Ԧ٦I.,=c`Hf8a'`W6ĻltU- 5:xih|6R%0w7ArGm'6o7}p…IBQL=k往?Nl,g`3ӒcGWcNtUpדNnkԤʧ}/KN5{t~a)ĜٳPZz]_[5A^y!n>A1pE%_7HVʒޡڙyvoOiHKn!#q/Y7ܠlknd@YKda?tV 2r}>rebCn>b֓bcavC$W^F wl]yDDb -s֘aXsHm=+H F s@^]eJ]1Bmu7Y.)b3tK% LٕͨGGO8$* uɭi*tf0 qOh -7(I..-~jxIJZmNoZNJ#^B5ںj ]܈i/0ϭ3WG7_Tn**y%`?~W!Wn}4Rz}qp<] -VV>8Y~q{R8"ͥ$9n*'6z{ 7[/RTKBb?kz.t -nEP] :RRu7Ľ lZw]s; f<3)>nC}D?P Cν3edEA})PSsq_i9v٣WEk)R^qwOGH-J>R+7G U!Tܲ{ÒP]܈EST~WkӂSdKu3ĵw2՘_5{o,eЈ͝ǎJoU)у{VӣS>܍Y[ aSDZeCN5<]n7TN7CSʠP>`4=YNZLPcx>vS^rx=}VWf;s!Hn*sctQӖIo5g.]s1`H͋4ԯ__ mt.\uYxU3v,7}>I`CaKKpvb FyHb^O=Rt,&5_G]}ϹFA -S|(ORTX[Z>57oۺ:mL= f;fҶԖ)HI9淮\ߊ*\MM[q;3z)_0>f)v[R'_qn:T -:-?X SnYTÁ ,bRRRzvʦ<ܹscƌzvYYYʦg0KfP_QЎ@#`z7 p 7p 7 >Anp@np zC=B 7 pn pnDoAnp@np 7 p 7  7 AnAn }kpSE |>j "jcÃ# -nہ;O_ LR͂?L-GLo֦cw;x#.>"_ՉS)?Ҽ\]ܵ"ku<PinPWw)w2> *4B5Lqiڋk i)Fe2nwq S\ :fRoCdQŒ9qj&]~샩BA]ܜ󖪟MNJr{zܢ -;!6'}̩8RX\ß "crw]W%KnϼvAEzuAp#bQ+-1n4RK>eGMyrڕΙځ=|nWl[p/ܲpx`5$^=cuG`. >yq 8XUe\SXqZk 6|3J"وeh=bICJ͡b힜=v*0Vo˚O|m9,\#]wø#M30W +ml^ѿ{_÷4=~MpV~ʚ<:puW?t#Ο;e|}LA3'6~sPAnF󇦎7niCݲңWQu;OV0' 6w <ܴr7ܮ!&A!ZzFuڪc|F^Lj Ս꫼(,m/jR -i3sTJ+% F̜'9Yvǥi{39O\X!Z^(%5+=R'!:̦"pp_Xm?{ꠉ $&^8g¸ǡ)wz X+q^ H$p1uO h?iY姯c8KezTV[g,2<7ĥ1lP9_=w*Ut%)aiٓTd n .{MC\ZI|Ww{ EWN:uy/>}%Mn,2Sf-K-#M2ZlmMe*&fSMnVv myC.WBG PSFcRyIܰJ3(9:YrB: 5jhuF'tΥ]Ӏq >DƜܠR(=BmݴEf-%;ekȦi*諀[0OJM .wkqY#{dmP^oT.h珀;CG)I8Q[8V>c5%- )vX)~3-'Xٽ]%adS=05DVSZ\>yzsǡ:qy B)\/9f9LZ$LV -Z}(=SRDt ȹVo$Ĉ;#%5(A UƢe{(7:gh>:J?n6t.dzdegZz2vZpKЗ[eV%Y) 3n}QJ;W -] RnP O::AU.g7{PJޙ6vMyWXB0u}NhDFFO-^%Y!X RR}0{쬕.nAּa€ټ~d<V8c|>iS|G7mlwzR 5c7 9 Mo=rP%ˑ*JrN;~MiMg_;nNXj)C{"֗Xc!])*yE6&{[vs2]Ca7 PFN)-oE7+yXFhVm/A`pl -%rҷ-AƘӷKbx]T6W ><1ti&ɪѧ⑴?THfuՂ -W5͗ psKtZodLMhxIsOP *Q=\t5+飢ƣ6v&w͝B q]ZFq!6vwoߒ F㸌ƄҧW - 5(جgcc67<͏g'6T~΢W'ɫ1~n|:VGMZ/Ŀ rC+;Ey'߈ƖY+wGaw{df80W535Tuw,Q'8& Ɏij ]*&NoTd#He<.Y Ν>v?Θ6}FWv꧂mbΨ1S.AisOm޺O|1s+3yDQ8ledaK6^$38!>057HU%$W=B!Y$1jR6OX2}),Ee%qM\3|_B$R~X}f[V4+$2W~}ݝ"oi.v^U ({{z -/s\lmܽT`)d,!,* I_FjkѪG,Ǘ:9Gׂ_n*LbJd;Ku/σpu_eզ+U`K~e5ݍY;م08|bmꉌ67]{Ps<æ -ھ/MxSyd`R$-~;Ydlݏe'8ٸyֶY$I96 gᘔKoKo -C²CxLJ'k)3XJJ;]~Sw> Gzq }!E2eǝ1v`xfT,Ky!sf *X_m0Xj6Pu!f܈6gӏA;|vdžZp~epv pn { -@ GPEeebb"b6mW#P/nz$p 7 pA 7 pAnAn7An77* p 7  7 AnAn An p 7 p 7p 7 NP(tp@_Qv=AtpUY2dpRh`7p܄ûRU6f El]9l̿y}Je+R..hU (R%۵k4=ܠ. 7K57.1fZ"ʸgO[Ea<[#]0&TY[m?ysTmG?G h:tZe]up#T].(TSQA/ عxUaI/77C*3bLCGIONn$ hL!SAG;mt|C7$*ݝԘ'FvM_Wك&~lJKL~T@|- ' YnFGBKM }6>ENKxg;jjA jm%&1!.6`ѹQaQINAgF$BB-Yx἞/=';;hӎ\-]xE z)?gEh[(fl1{OΪ}6QdˮM+RcMGN ꒜,hEla3̾rIիË7ptm/Ր2L<4`2οM|SNGAfJ^Dɲn/-o#x -e5K2ws0]f0(n=fEi3]>SAl&l - V5v}@n2^x,\Y9C*EjxZq6|FYQ>$GWgQaM"4)bCGɚ mGZNۦ9yb2ވԭ! M0T޵@UBI/b"ܖ7xWlmO-Ozwv=,fjL&Tz e0wOnBmǻ8WmJ;yǝ 2Ftϖ}m?iq0Lp#r%%;fw.Z}ўi.Ķ8oڰY z =d'Hv#WL"w,n)XsRo -fܹKR_w Fo7œ֜)UjŕF#4Xw7loSU蜜w l s*TpB B}y>YU -*}p¼RֹyymMi^4޳N_?nxľ;X -Lj,3ȹo-A n٦{/'#pV^y;](Wa "_;DYiU3;hʐ fMicHAK~q#-e욂4Zee"0/E:7mTZ02n:NͿoݤP{ekp4{쬕Uw7Pi%ŵĎOջ6o<ߌyꞡY_ -wW?¸7/Ι6 {";$7ǍkT]pCtA|FཬoM{Ȝyؽ3p_2uu8S^]pzd.gVR 6>[Ѯh3w}xp@FT -/ :ܯ#s԰b;/i Si9卝\QZ/c@5JT]Z3 --9,8 >ԆҢVN#(_*+sL r+H-|I3V 䤞dJv5F>3YZzJi :Ĕ#kM?MD{cQ0T#1MiTiӦH+էuɡP¾-K$Q FA߾&^e;ҥ) "TCW#}%{Gk欵g=>5ѷW4nfMC#f0ʪ3ʘ0q 7KZk+R(Thz}YԩT t}T75A~*˳פQJq__=_`n_cZ'2mEW{+dv_͛vtIh=[_QVV(cTdXOU~bmKM(j{o x6q^75 G)Z=砥u·ICTO>λMYݜ&>!+Pp^?sux59^oQ$6\ST1I'\{ྨen5uW#_dC33U%53{(g(|ak+`oƚe()Ȫf)ƒXS"<椱Tvy{jdR4W-jin(ʸI/^AjFx-2zܤϽ|2 E] Y@0jAlT" gqQGT[߁Zu.C+eJ@ $7 #@T0އLJ{ɽ7|=߹'ǂ{64zrQGO(i>~尛-«Vᰚb:I69a&4a-yqDƎ*m4#:d\;m剁qs8$[BA*6vvW'K+ⴀ%K`荸wFr4737Bi*ͿN47i2u_#9R521G#)ےg1 :-(*nk( n^uw47'ιǔIhU߮K!ںrq -qCkpXfv$9LJC W毘agh:1_F+*=MT"](~F--=m(j::CO[z~K7 'l=*37'joW 7ͪg|oGg!nN= 7V B' - 8òeˠ)rߓˡL uuu} <<<)rSli ( D"Cqh7@ n AP@P@q n 7@ nZo n! n 7qq n77@q n 7@ n 7@q 7q n 7@ nq n @q n 7@q7@]lByrXU ,(kRqŝ~;ZKBe^&O ;ɉ47%dtg3V#i雬=Goc2#)R?~yo*[ n=4mŤ(nJINdk1斎Nb^c z}[ yulh?Ռ\wڃ{e{K+qÌǬS`} !.WzrΟMrΕg V9P"lAGǑH$q,:$#}X6Å^"!@#:tK{0V$ 828n}i/Vy=ac}mYi)zD(//љcS|`@nq})8%{nvܥp7%\'8``qHΙn&y?jjΧ)>j808 JܴJ(ԟ$^%{Sif, :C92>6 ng Cigo8h+hk]es6zH c'L UhLIA2n^3ǫ4O\9ё~nD=˸#;iS΅w^ GjHi f;x#]C8*x]FFp)f:v'Q<D#!IҺr#G<*$q X)ʘw:Ziu1 5ͨhW3o׾C_Iy݃ -~:{ 'ΗS1y:u;˷*4^K׍Ls>2^sO򖷸)n!XsՒD 1,;q91JrE.za;HJUo]C=GsG:7UVܩ'vf7m3a2o%`ͼrٙ]K7~3n^+286aA$Y"#6PU]n7 w%V)L-ϸ ~.}*$q#ĄM*k/XXcE"ǦHmS buƯ.pCt>q-oÆN@+yDmsJDHdsZ;Do8)L~E@k?M=r#ŭ1HBQ#i\$zITUKtJLWLok4ַ50T_-;WS0(Eܹ~YE->DO)_ۅyw #N%ڢA~yZHkoPhD `TΓk-4zrY/_h -'6(V SExU樜F@[5B:@׻nuI7̵?m %YpCwۆ~1uipUKSjjiMʌJ|6WcK͜IP5LL/;nq?>{N-I]rkS8mdVt^dy%%2/lkb~o2N*aѷKW.MNìK(LIs[}(3C'8;o2[8i e;,Fꕙ{Ny:b1tnIx*nŽ'ZJС[G/.fZ^]c|QT+/̟\l\Cgx &侞 -am_IkཟcfN*r߾bW|Ny~n^!s|&ƞMY]gQs〙O \03QerPB)^G/B!~PC.brΕsUh4^$'PBz#v~?J_]MS'<̭{S6IGÎ0 Z_斴.U|b8ZǸG%nQi^: "q|^o?{ %P0bbbF 8Cbb"T6E.zwaB`25 -܇>hCڱ, -endstream -endobj -98 0 obj -<< -/Type /Action -/S /URI -/URI (https://www.zkhashbounties.info/) ->> -endobj -99 0 obj -<< -/S /GoTo -/D (cite.EPRINT:SzeAshDho20) ->> -endobj -100 0 obj -<< -/S /GoTo -/D (cite.AC:AGRRT16) ->> -endobj -101 0 obj -<< -/F38 168 0 R -/F142 178 0 R -/F29 176 0 R -/F154 180 0 R ->> -endobj -102 0 obj -<< -/Length 22232 -/Type /XObject -/Subtype /Image -/Width 659 -/Height 215 -/BitsPerComponent 8 -/ColorSpace /DeviceRGB -/SMask 181 0 R -/Filter /FlateDecode ->> -stream -x\T I;wXuu@AD V,BR Q: "뺊Us?|՝s{RTOP{dd$ء!$jڠ"""P/(@ f \rJ*sv];TmVn}PZsM>Cm| ;PPn77777 -777 -7 pCpCpn77777 -777 -Pl:8vf'ҳxr/bִ)?odˀ0 >zh0-0_7.> ܻz5t'Z,zUwM{^Hx~#e6 -mBjAVJ-/)B>|s_zeˢ`*Fܵ|SIprRhm oeg^hAi^+LtrHᳫeXq6֦g"JhE'b"ﲈ|AϋƑvVq(΄~zZjQ6*/~Y#֝oN~Kbio|'po5 .d}Mۈo'pˠ%O\nkr} lO'_%l6 -)л6yQﱦc'3-z8 1aY%ENwȫa RܼJm͖6yMD2ǯVH*ΎG'xmV/q)됏 -rWj|/|6y_wqT,h3$Lb+sDȭ ߑ^{|":c^zIr/DM'y\N2hu:\*t&uV\.?/%Z>Oac6'W2}YA6bSH' M[oR|OZkf^eC|6({J'z >;d2x5֏qhNqz =o`M؅+5WX;GoK~Wetku\i:7&.=eo3^xM:M#Q> ~k#𺑄u/;}Zi]6w4xG2>璀6`#?+lK gA:^oP4{e̚,rnBP [F1lQ?cAs|584Mⲙ5Qyp-9~.,BѠzϣZ& : ҝ7^]zq}c)vBJ]J?Ā&^蔧{͓ ɨZp9g&wܨ\)Du y%[5g0y` tisxE2Ja\EܗNwy>>`KHMG -.U>{>LmrĎ6yW4#&7!H m -FSޡ=0Y} iFAXrQ߷_{qVpg7( :#$tT4MjE ԭbwMc!tizpAK9[H)6jE]#ԍ+mI. фdkڿΘyqKOGCzt)l2 VWӰU~O* -~Wd)5f6 - ^DPRHEPn~ކ֞ ^EY׺OΙ{b~C~|{ߥfKTgm\2n). -@y8~ -H<,#+\M|*I >*K9(f -я,YQ+<fQFMQr9чm`9"3M,F{g 6ǗeƗ|Rk2034'qk9b>-~ lݹ)\[e3ÛÓr~ͯ_( |R*(;3r˓]WG^Ego𻮵? -pS&k~@i,/hhͥnqǡ=- n|a#g䬊tHM^2BpBɮ7PƮ:>o( ->xOҪ9~l01&~KA,B߰SGɂ8%IZBɫa{'za \7|}=sƚg`Uls)|.z695<ak욖aS8{TgqNvͼX1:x; bcL' /&Y`>]'x/mx6{78U}Jdic=krMN5Lr-?^U ĒkX - 6>L&S&KO8҅"$o;$9N"'k ;zM NEQ֒oyf"gUԕeoNG޳u%vP߸g*Aėdx!|GxhW{ -㎜bYz h:2}s`zdyL*N>rl{+OS~<`՝>![=^~Ɠҫ|VIh= It{LM'5IռndS*)0Cd@i_m%iNADZ'I~O=>n>"ď>ml//|w\vSDQ0{H񃊏r ֺ!vk07 m]ñ Qvtjh,iF%OZFbf|:Ez 2j9L,h% yF߮xGDm:egM&k)&b}EF Kl$=J^YqTl8M.Lϥ~ {N=aM^pS߰n޾ hQ(kbw\l?g^bU l>ZZN.1m^ӖԶ(9SDK=h?6ɓuж-e [T98,K5| -h>?/Y~Xv"HMGOF,&mAN-Hm|) M!']{لMnמ0h5+~u&ܲxs+sɅYI+~gLpCQ-W/] QlWyxP~#p(mз{Yx;N,~(GXmۏ gPXQZ'Ɩ#q|Cn7(T9_Pn(PPܥee2 ~x<7%%Ƅvh)5 -j]xqڴi`6(ooo_TjcB~Gavh)ٍ7j=x`Μ9`6$7An An n7 p 7p 7pnAn7An7 p 7 pn5t|a]_0Q -eU`3w -UL|"nO v090T7?#CNFFwIk@nw9,o'.FW B\Wn_4;ET$6nLΌJi7eKG#sڸ= |1>2":)ˇ}/pgYYJogy47MMJs#aZ\;>m ҊMfGYh̿X:4 u6Xk7ˮ7]UDsB!(=亦]SJjC`PkJԼof^@p&:h st1O=2#-( ( :4&_+{e5n`Knܹw\`*wwiԢˉl&V}K(L[]՝;mO5WM/..IN^Ep6"6.$͊Ƀn?/\dڄ6q/0uNO͢6wzV{gr&; 8~'oo_C# ->wTdn?UH"^;~U` /O6Hmꦋ[4"XT8%P_;OJw -[22,.z22 .KsG-znr2>?14(_5sN g]{] 12w@Å}'3`%++.0i;Ҫ\8p_0Uv H@<3}l n -LG}Sؤ7? +)wYYW_qxFKlr{ׄ"\6WrƫBT]o[6tm2jG+ʯ?Qz4%xdˑg5}pcTdY9璸wY{v;dvBh^$I4gD9Ko4JKՕG/v -偳ZBO=ۆ7<԰6ٹyְLN=.ߛ\P+ah`U4u+X5g5d]L/UoҾko}F}dY{!@ܢfc%NԾDjʽ_E⛽NSp{߯Os~ -c p=&h܍gVoܧos+)MFܒ1Ϩ]vF+ٍ^u mXAO5edTq-/=P ?&vUa( -{.iex" E!0)jǞ5L,ߤ9yUدKvC㺢ʐL:WTǥVOP'?QZ];w7.KYEAq8ɂ4-}rwUnI?Cypao>uC_~(8t%TtlY YP4qOO!e6)ZWW)c(l8)$ލC3/y?]KӋKm=gh{bBm NYَaxOXww%&]:K chsF#[\~Pp lƣCTTKj -kw=55d2ht[mßPȷ>Zvseښj{;t'a%՞C'WG}𒓛\%z5Dž]:h?BXӊp8_gJ29 3kkHjܾwi;I@AB3 ߎ=? -j-pov*))KNN97~$U+м@e o7Y41wmWs n-eed/X,O2ẅ'voi}eiJrs}p3U/mߎ=ѵb$cp˨a+()kN^}ԭEhU'+#]Y_>oh^jh%m۫?:/h!kaFYS++ow7{?Y?0AS{{[&{|Ω^#}0!Ū*:eͥY\;3c_6.`"P+UNpn[? jXC{wVmpAp Ex)_evqO" pA 7 ܠ -J]t*P vh)M0A]]j;k׮`6 *WGG {`Ҋ+z vP{`6ldzAmL999`vfϞ vhpn   p 7 pA 7 pA 7 Anp@npn7 p 7  7 =uWi pܠO>pLM]:9'N'X`47 b32<,?a¸'1c]vq'9C;;*~1T,46ܜ -*]S]M5F&4D:,з,=jy`DjΖoɜŠUX,P_P\K5I.E27}@H nfګ(?dH0cGO]ѽ Kkf1wwRQHp+H]1i~Si%7uՌ~. ql{>T77xq}S Ν7X9o\>c 0QU2sXu/9>WS_pKS.wwM#G:vp;}J#\7mފs%3g͒9qtNei;o&߃^n{1(/Ó?syuڋiض.7謱 /EcL -1s@?V?,M?ZMznF1!AD%Z{fH(Lʲ27G}fZuVqrilw!.힮1dE= r }tL^%Tp)t,NqJP5Yc+vͫρVgS<"y͚}StU]qȄ)%c5o‰ j]{yVklkt҅.FtdWz4weS4EZ+ddm ,S -r`E~7Xvv* *rȄU; uSCZ<;Z=Ť~uO^S\) Jr>rwV}wNP{ȰѣG6yR5zva^3=ݡې|fIY9 4zpR97jG'(+5W.^~T7L6w؋+2@tB\ޯB?\C !@- np=;v5ѻ4b񛎊reH{z>$t"i|lj! .ғ edqzٸ[G)jq%X4^p@k1;HM\6?VFggwWr&L}z!*peZ L;:!*-a]oxx5ufWA(Zܘ5RQ1,&YgnALl  T횀 -Y{&ྫj}p34Ueb<>;O^N+&7vNK \;mEŚ;iЧMۿ0#\Hj!wvI [ҊZL]1T]Ye\F9Ef$3A'K4WVF]J)F۬Gt}ݕj]4Ha - -K 1y1)*賙ֺC7^zğ)?֯KTed^ONc&ʧ@|].,6 RLDSe22n5 4_jϦ/wc8f@4'$\jOQi@~C;]h9JrI2@u A^ mRǹ;c\~+/;g1nHm~YW5^zK]=??8p͸Tߧ퇂`ϯ71î{ -D#tm͸ewRi,sQ{/qŒ|` rQ'/T+J$\kO!cU妮ګD;l[1ǩU󆩯9 k֯Mjp3"=4\v;州F.ޤy}J*9u҄뷢gOzC:mzm A]5 vYl6Zꙵhi="l:isؾc_3oYk#lQY"+3}J37 ̏ H}ebϨl}+.O8f(6]2fϓXq4/Zga玳e$]4VVF&TBg\xmgL[[йUZ?t-[w,sCAɿr\Qjwɨ8xJ%7Ga2s(+LvpKq7QMFH%Y^ -q|ml3No5yT.\" K~},C --V0۶;k Нp߼~TUKZ*\AtA.P_I}^Üj08 D -? k 7 d2 jcDvh)3j]reƌ`6wa \Y @Duuu/D1!ZJvvvƍ;ZA<3gء *)) ;Anp@np@n7 p 7  7 n7An An pn7 p 7p 7; nAn7An7 p 7 pn pn\:|0u5kM@(n~5 ZN̵=2=k; Cz˝ -}3zWhe_E;㪼B Աp@-nkt]0oS'qd$Y3ӋμijOCFܜ._2jܬ}]\Z‘3MG%oN1i: D\LZ"oRݺnՃ5֛[ts(|U<ʑFuncu߽MYQi>Qc%87y3%OUFkxÓ+:&(׷vOsy%35ʉz٧wM{O?;ޭةO1EEY}Bpۃv4j5P+muEc}:.p\"~scJjsvIԣr)uP.'+fͩsBiMc;RTš"F\;tTK׃;+D3!:AlWA:'LZ}-[Gmѹ<ӿknKb((wϦJ n$w.[ ܄[ZwB=NCC/!yYiD.LB;k1ѫ3*;p-^6}PxԖl,7Zc51lU5޼"7?N~olwBVgJx3r -0n7o]ڵ_񙫯59!]0q\ k郺.zۖqg["C9D63`zc׃ˬ9|R 'C{.c.iMbacdC;zr>ho)ŴdSI3,Ma,RA-'`|ߡ'(r4 N'LI;GwXp@[vk=sFL^.=W|9_Xm~G$GZv -.7ȟ'(i4C:o\ -'4 +*rSmʞ3I6z; O#B=ջ |ϑUW);فvT8FͶFFCl~M3\~jR-Qs6"UG/MRՎ'KFU a~GVN9 - -51*i${2T,jrѵMMe=l?0=葬z$a wF1E(; f`# ;XV[paC=뾼jN -pi JqĻhMSDO>Ay)+ ڝ#:tС5]~ ؽdYw]s? QS}m`oQK'p =z,=,RwZm6Z-K˦2$ t<3}nmuEtY[UdQ$ܱ[o}鳙Wlɧ5^x#rc^,8ut1ŕ*(JNC@>v_V]VVۺ7<1ܨq! Q2jsiM*1CUQ&Bm}񺣴wFd8ʼn"oԾKX8PL%o:JdMNÀ` I>Zjpt[7Z~`ŴW)q:=[p:Hݱ˦nt2ljJlKׁccקʔ 0SJ$K2ðT.JéWM14%F{ȫrPz=<< ZRe^H_ߡ֗֍s+prʹ?=RZ[;Ɠ0 -۷{C;yٕwxzg+)^C{)+vu׹r41#gYcĔc/XAۗTd;}YsFe޿OΛ:HV籰dY:ڙ fDo7Cde,^\(;fyz&Z{Rʍ#s;w}劕2iDjCчukӅx nnreջ :}ء+NSG2xZ YܱϞ:Oܖ,Ǵ%/MN(q?-[rŲQM&YteSm1{kVXj*+)v'XlŪKv\1Fp)-_avoS6w˫AVTMoKToZ pnwmER{0jq%Ynr~YB ۘܠ܂´X  Onn6&ژ߽-%CCÇ@ I&ڠQ/(@ Hd}XfM}?*ꤸH-qI$󿂛ɠf$[A A@)ZfLBJ j;Ίv9<},)ZIz xh`~WXU]][Nv7o\<[A`+w8K't6<* 1 jp'ԽtV,U7R>3Lg~3[ܿ_;d٣FsQ24r NX$epDŽd00|K- E>M4YW; |x0pW?{A cyi!b㳘OcWL-Lg \Xk_Sf-7F;X?\ɤSCMn}->I'=w: gph,{Wlೣ{W>s9{hpɤ7s W=)C +k;i/٤+=z`A>A_ nZ%TP$Z( R\:2W{iVG}@[w464dꠁ#g$b/nZ+$"&:_[AJ"kb癐b'>xiGF\?}%$n5w^]U}+ʝ,{i֨]z\\ܤ ',\{}??<=6vڔ LoVTRY'|_Sl47M:vJ)b -Pk&JlMM)zQ^=+'X 1jseQ\kn QKx´䀗 n?oܩ"I-0LUI~۹~WIb;ٸ e -rIp3z]O$:kQP}*T&  viTu~|҈ +c+0W2P2_3qma7jG'v44#%k?֚Mk*R= _WhC>w[2{Ұ1O7DEQEn Ҭ>7V}e߂W2*' ()odڐW7N,ӆ Dзp(π0uOXmU^9g,vՏ5<PYV-ukvԩf2U_U.T'?;.)Gvh߁?h|yPm>e_tRіֺrmktC[c5Eac*{&pFT&F"׷ܠsTx~Mƚ1|p3ig.ޓv+\ܢEcv]FAQ]ˑlV%(yD|֐֟Ţ1|Ԭ$j@}y"p|AGl9{N%D;#34 -Ķ9=M%P6NNa @(ʋ[0ϰ[ZYK[عB>_9Xt~AF)>LA7F}we9xr^bӫ UّΨK?\M-dшًwJa)@r^x[ptp3D...DNBx:h&dfR^mQvQ @_ nkf*nЩg)GӨd3zjarx5d{Xdg>8יq٪JrVE}x:0UVTq&YcLއ9Rr{{{c$pZuޟ:a)ySPa}msf<کzdE٬v=Ɍݿƈ~ /պ~PUMUVFFSDt{(3;᭩J[?͎ܔ*cSRV6o{JJEeUXkTXMEڽ)ymM]B;yv]JalJAna]QUYm尧WNE,+))AQSSTTlkbYo:3QUeAYh4g.}LsN3O13c_r'j^A0B4rWδu3!ȿAmܠ7Ԛj㒉o5gf 7~a(-TåL^Ł 7vgœpn'7 pA 7'ŋ1!ϟ?R?~: -3fL׮]mPÆR7BAϞ=kjcB~;VZճgOhѢڠ:Rw]]K"@mLEEE`ݸqVЃ̙vhJJJpn   p 7 pA 7 pA 7 Anp@npn7 p 7  7 n0An An p 7 p 7p 7AnAn7An7 p 7'w1`"w J(9ྜྷzWn7Բv:U+SR`ߟnV6r]`xusuNIu̅L79A'1w7jp ԰6vԘQϚ36'~q\s&O:w!3Rx{"_}]k%P(䰙 rHFL._fb1xfpüޑyo7!/ %͜psETPd?9E^5b̬8#[(p(uT^-de%p^ AnP+K?o={٧os85[O ;ԷĈ,bwfqi %MSUA3D#8jxLHm Q(ٳפYʩNszd~/Y}|ԹiMo}FG,wFGo] ~ؐ^*MZu@,Ca})ڶvG^d="Iu0>"롶䴋8{YO}lZMU|ზ1͎v>~r -(ΞO xh-Tj;$,:D>}v~Y j:`(6n: -heg7b#ֵUԦYow껇؈]a=\.WxON"9j Lza}="_𵜻'xQ>FMw,}1l -8njϗ5{eqEʛ;=VqsRR}3h!8oʽZ=D_>8Mcb^Gw2n>nwi[l~ -׮|,IFJE޴1&:*=nS xOuQy`EзMïsGuMϯWԟ,ހ^}=kiN;0.G:[Xg~Y :LwDz&##m9֏/&/,2kzG~p?x!IM埦ʭw,9w4!GIZ`ES~]@pNI*JjH/_87.?Lکc`9&(g#hYTdQ"DQ(VZu-jE. XT)((*(RQ kL2C{ -H?yIΛ}w^צqKp8RVa' I-:^郎bj)znY< -j[$^ALoY9USLngH›M^[͒:Ec7y%j D{%wٌ!ʪëI1t_aAo - h5`P25 -~߉2sF(cGQuOȬp *Z[*N&,7+ -nD"hݾ5Z[{_B|=խn7gq2~.xVǕ݁eUu]=n7Eo[Ko'] - pN{1;us:W ~N^fgǃD)xu tM퍓莈Kb$'!7ʭԆU,C&U.n_x܎ngڥ^wWь-G*>pjyZΤ8UHW*[yv*p9(*7:CҞJBPW`qӾ;| pGn/0!A sb#ذ}UT%/=OߏC$jA-ύcnۏD|;|c}o{SU9҈2>NR\6?K~ϭv)yBH8RM~gP {v_Xh:~CE&r*ϥau)"t/Y[br\(l tz"O\Y: vJ槶YWͦAN<ھYrJd٬<ٟoIZir^&iW 6*Pw͸mЃkgl ZSJ (ݤ;#BMmt$"mGH<"eiLͿeݱ5֏I%[o?LgMEٶ'J(BQrIz#͂֬]{`|MQ[7hLYmʧWML -^g)M4L.*TGnQvr8-dcw?.Fʝ M쾜e7ފDĮ#)sPB[fKKUdie|Eugp=Hƺ@Fͻw-uEJZ ƒWXl|\lՋ܃ܠ -mK;An>5,v0%5euyEЁ>wJ|dOIE ?p@ -[W 7 An7,@ s? -endstream -endobj -103 0 obj -<< -/Length 22666 -/Type /XObject -/Subtype /Image -/Width 658 -/Height 215 -/BitsPerComponent 8 -/ColorSpace /DeviceRGB -/SMask 182 0 R -/Filter /FlateDecode ->> -stream -xXTKǗPB.{xl[1DJ ADZ;w6XVD -~qOff9jii~j'CSf)T% E`&,5yAHUj5p<ȇ&LhULfEj닍@V !0U 2 Rs=P )$0$6$6RHlڐH! !!A fԆԆ@ -  PP)$0$6$6$lڐH!AAjCjC fԆԆ PP)$H`6H@mH@mlڐڐ BAjCjC mgp~c'%WNn2Oϕ'EgUsUGH¡Kzpـ >f62Og t&aI$w>ժSΦ/ WMj$T -Z:rIi)̬[ڝ/?챇/H-=ʈ,Q~29d2?(܉{-ԏ$Kٞ^z_W6EOH$:fOO-"ѫ{ɉ%{ >6e,[t3tKڼDa[L;>Z,Qe|ǯj{Xv@mKo x~Dx}'q}e br_}+$7sxaMvf֒7jԾ'>%Y|W `Ν\Y7n ?>Yu_dm4fգ^_,K9riQܪjm VDLbc -}RcXVP(;`C8NXS?@$kHd'|橞d@Dm["vV_,.ZVin9jzVBSvKO"=sQ~M8'j9FUY8"@x|ړzFT~mϩ"PtF4tQD1I TH"cl5!'!26 F(XL$l0)!^DPE8B0"#@TLH*՟J¯?e%~Op -"D;'j_߈`y| -2Z+Jq4 -"')9;{ydUVUD_ Ub 8xQPN1dDS= #b9p~P!A$TÎ Zb·qC>!QthL&4o@&p?wN|̾ncDDeU>4+Mb -,[t{,.jF_P ѵʩ5eb} $_6(<KRx,?":X,IT1ECl& tELPc;X~j?AK'G@&Ϙ;ڄjE,d`|<]$q,6_ʩR(ZpbMcι| M&p$h&T)z$aq8CԦB^O~Z}f5)XKd2ݭ$+hMԾW.]~"Y4 -/#2Idz5#T8, )zFEhSI ,K_j9 ] -HSE1lit@Ib,P(:Esh,/qCm>jVPh^h|pÙ;ƛK'RR1=atr7T0)"\&!u߈k=}^K=qnSh6Ųt!CsubP+͏*rۀ&zf6N;T6֓WC*`n|]rMH8I|nLBC=}XGmgDYm œcv\6QS4IAO]$TI~Je ktzfNO wL9l{Bkv\aRIsS Kfb~Y4K2[Pگ@p%i>d>GK9٥Yg1X!}7풅wEjdK̑GaT$/Cm\^K$U!PsݏCL (x^.^0;^vQb5\*%|}:Di,rXXZy} -̣A^1̓&y\zcn-RٱQP?Hm*TZE*#y<^ZJ]+!Ur=h@m8|*wNjKxI.S9wj/fG5y^sCo A+Z4k ]Utc6֋"uQ7ݓѿU#XiZjR@9z?\DI^{{k/?n\.ray md3a^Nh9R9ߢ OP-"޹\z`/eG|,_|aF-zv|G2!b7NZolԖ{$jx'kNZB|aO3(C"fY 6@ H7#?SxYb@+GCVsJ44{t&Ȕ̵v565Q[#c+ϩPTjkbj닖~^VJ 74-Dڟ0'Y}P+yZ.b!Fyzz#3˫()0{k縹˒EqL6;VXz/SMi:hF%<$Kʽryj|Կ!+eKw%oU} -{Ͽ/}>=`c6Dq6S !^4qQL_¢̈p GF]U҄F tܗf5:Kj@X4d- }GnHz {^?ac$k+Cgh|(H1M -kkJW-a&\Cs4 +8x?~_@2uM,&OEf%π3hFWd&H&>bx +ӘxjrN1ڔcP-\Eo2XMTiO\D3̼2 A* *2DT rBE9ԓ* -a(aȨ('^EZiE劣rA<|<晫0<ߩm"6Ge96!5,si*qL"Ǥ\<|-C>DjIdjyt8xas*r2bL1E4ៈlt߷.Es!kkELW,2bQhHnnfxQDj%Vx0yKVIfo=k}UFY4/zQx6d&~+p Vh@[CeqL\"}OVTs܊q qDj -/Lfii9pɪ&!U(Kc2K he)up=" r<-1!\~ _(߲2 ( -=3bI^TJvM`JCoDWIOi'w7EHs-eTWWV~6q[GUBfqWI,:Wy柝Z&WX}L#ROٲs#0 ͯ,(mL_++zFkPۈVCff[S -r .hN8,_c?wźǬNٱ|/t|@tބcY= dl.q_DB.^^[t!U^0ҪMt-ih'ƅF^ vn\  ,DsCf~%7Ot!!9#=9IӗcB55vu>'(Ot(@F)c2[_⧌.T1 ܴfj*E'UځRR&o3mm sDb%Q}!wىcGO%hI~ٵl#PӛCdV!tBֱɨ-1b|;v򍇩yU83mxjOX"|NGӫY,e;Qh Qo.QJm5o -)'{k&E E1<۳HUE8iS;AfR2uU֟=hug7;{ϗetJ"%qϖ궹Fg;2lJDl~JWfS7u4;2MϒmJQT3艱F9NsQ LwŶiwkIvx}U- 6-y<N|`_T(,Keq!vlE',GTܯ} jڜYQP>mmR?#_e{hi+e7η+amm=d8mȄnRJ]כHbϤĩsPM?]㝩w - -KH}FKх|FfB̲]5Ֆ, S[5hj5j[}m׺\ۻg|:v֗-QҚ3+*7mPs"5^F=xGgFu#ѭszlʝzGPzBz W[ 珝o$>g;JgRmBbB O>. ďn^ڵy%Q,6.?TҐ oZTΥ15ںGV˱%$3iԞ<{20&JuƼ -\˃t&ϵ?g,[Y끳X0=<XaAZ޵(bMco؝44,$}mjIP*λ5f;˗X <ת~׻RCιzQYZMYW6ix9-7{h*Zmm\qM->|8ޑ\MIڡ}m[+S*ė?GϵcqQVAI|jוDI}&z(7X(6JE\߷P '=3Y rwI<%jRplyjˏˋŠS>Jmpx@rw@߷[G ҝ3F;.fgujr*VG~#ꚝ^_CEO>Ji AmڜwFT`<߬LZϮz*\JEmR{U-C$+ޓu3]WU;iVWBWڿ YmGO2j{;zی$ -Zem_zyMߚ4wlF\9߰3T~ -TkU$d}?+]R֢B=99n˩pk ޘ&o+({.}<~1"Cuʻ=<$+v kǨkcdsL+6.5$՝Nc|| uB.A2c>RMk --O$vg h^eq%9/4` -QMep߻]|hZ7 -, nFe6PI;vT(nUy]ݒ'bU[\T1sJNS﫠 [km_f#'ۮ:_j%(-+u;x&Im&{wgCrYa Z壧b x9C{О m jf+y !@`6PԆ@ -~sj@ UT]]fBvvvM(*A&l.z6PfA@m  !l 6 @ - 6RjHA`6PA @@mP)fA@m 0 j l 6  6R AHA-l0rYPk@F *AHf}gpPio -A$ D2EmanOlHP@Mc6& *G!~j3",gCWncl|8ma}5`US;lHNhl>ݺt8{mWOV`:v,q?GeswW=s5uZSyTDe@5>m֒ h-۸lF.C? ~jr2@V┙WBUSUaZ Lbn}7E'|d;\mh"v6ct)~OJT3sHKNi "+Ttg5T!IƢ+x ([=X;!Qcۇ|aN\,m_rM۹SqŠo߶ɅPL?j -hAVi^O͝]PN!zV{'[{[} M 2ٿJ B6 έgAI5 df`#{(bL_ȬiHm z' o;v?؀=n:#uq9d\W7/V1~NB&x醇>}r`nl"~>4|q%E֒zrrso߷~l}]W%Ӈpಝ53N1@X$+Ք%<^iG 'h}͇of1 ֘8ר]S?kظ EehF3'fS[em[2_r45eyjg;)*ie%|*a³'SAYCknv1k~BX=4/6~;}^νTQXݶk@4DSW\r$I1Ndwv}>i`NhR=S+%Ո"ѱ6EIIs֊GK(St.>¬;d  j`T]#_v»J"JI]kf`>W3d{騀QPT>yk8]ٿC EZ*>uhuS.:dL^zƄQH9 FIzfnV 6IUelMr<Mal֡)*DR=x-@5;12 ӨaC[Wq+.I`TsDʱ[t;J=h5Cz"@dj2F#{-L'&'/e;yJ,L>W7զ;/n=6,ַ>"M:t`j AW1YkW6j'u%.&coY";\,FEӯEO)!Rluߐt\򨶊C"'  ajKU֍)ۡ//`0)i*ulH턗lϩ6^RJmBsjWkλAadj~i u=!&A_[ڜ)t|d=Nɫs`I v, S8GCpj׍3sQpr%5!Nx=j%4C[}uQZ/#fc*>~s{kuF'.ꘟD03Z50"}ftTP '/20 l^!cMh,VxJ#j3)\.C^R1R| -{)*\)m>&bJv)6;=.7OH6E<ۥ2/؝\}ߐڨ,қT7B^VAQ5 ȅ!wNjqbrSxiӱRof -2)9 :`L/wv9"vlIxf]7-i]oIonV)e}ks) (-]sA*o&^J UnZ}B?I.qK^0GLaIʜW=tR -O^IE1c v?W)+L6;}=V68iH͛8`@_5Zt>!ey6 6x?਀lЏF|xYStK=B%VFRUb0{?&T(+zZX\E<]4k 4.2GB G73ns7V_DX@fOPopOk75YK'6 O6[MCq}m:c$r N'T1^[m6PjVs^W&&> *++TRVx'IjAh6oFW@mi_Bh!@-l27PԆ@ -@C'Nfj߾e PO@@`6P3=3ffRQQ:t(C -J 0Lfvtt|hBU 2f5w  0 jC l 6 fA@mR !@`6PԆ@ - 6)jHA 0 jC @@mPfA@m  !l 6 @ - 6RjHA`6PA @@mP)f|j |N$@m PsM(L~#^ ,}e3uB8|٬MBx?4U{H$<8d&!ت%QǦ?d뾀N|3B8P@mP1ǡR"[[D"nrfR{ VC`+34Նꭼ鈗v%}o_[h+GmږY5;g@oIN{qPN 9rt>ztUZLݫ&a0:ODI{?|_@mP 04j֢sY'U7[%5}tVCm X1=yЉ+eGy~mvρg2P9Q0-Cd~6X[ >un9tIG,v]_hfwA(`,GNo.~.f?C!n<~ᮓw=pEf89Zu<mj ˆhLsk&}B'w>}Ɖ2j pP_L@z3-X]($ȩM͚Ա4;>Cf -k좩5ؔɝ.UU^W@crvG$;Ԧ]l%Cܣ'm}P%!plqspĕ7eUu#9v - -PX?M,g QK,W [/RTK  Tqkf*-> -AYOf}<8 62ڲE Q9PVVRw`G - -=+ JS09Y]nc~i,Tvسn&s6Sϑ^\ަuE vSU~ۨ٠&0[MQ]`S3d8m,}&.5o|[e @jXι嚌޾;+yךch)Rj;+Ĕ0TwеEYCwmtd'l*jdxw$Ju֣2&砚= 'Om!g^s oHqUR|*+j$߹VHelp:LsThT[AMd6!21L69amP6V:R7NEy -3Š&MįģMQQ1OXhqD> `BE˧:ѻV*Aڃnraj3S[(?7|7*B[8;%7E$e˻uhs5IDX'g/ӗ^ՓOy-Ytj7%'&$>7գ`ψϩgYf.\tOwkAmajޭ+'LY`zH.sNPA2vFV!E;VNm[mybc+L^LBZq)EݕxmfIj׍3f9Un@/f6oYziNu&gSV# ~JcmI6k(Ӯl=8iЩ+髤$9fw5u9o;'y_QTL>:"P9M Lslf{KsS[O酪\@,]WR5m>2W'K3]Ue ݐ 9]=Ԧjx9¨]| s2{dvѷ-SFc`%<$l=T¢+5FeVVrJl6fYdݙ?:%Du*b8bDS^պ!=4|NEJk] BotHĉ!)+\zQ$B!ơ?->h.\>()w /Ca579 z*T&\CUqd]q375|II%aT]BRE[k{~}ר6a⻒Hs- - }[u+|F>:EHzuj~l5^Z^ԥU SS:-){^/GvsSj8ҝ ^))*,Y&GShs^6rb?XI̅B#c='K>)ʡSli`ESR2'9υXuMQ#((`q8t?F7YcƌVYYK?о /5`ڽg]J_Nb7-[I zwQn[ J2Bg 7PgaĬCei9&&9hrr_ݰ+,7?Ec>t`O4?茜9Vx.(t9(sŴ.UTfk˱-t8` v v|u*5M鯬կo˦;W1ɥhKWm4[4`@4f}- f0 ,ϙ6@)]|ީ铆7eѥOf3 }Q/5PX)~Æ6s0DD.Bs{|qԱ::c6-!FU~-z:MS3ڠ߶6+`qsv0[__AMh6rcO, go} ߾jڍK/8h-7vCVP7Mu*!qRw|g jC Zٰi~,jHAo6a 36 ׮]Աc 6@>4ЪQM>}67ʐ!C PhUL@]٠LvvvM(*A&l.z6PfA@m  !l 6 @ - 6RjHA`6PA @@mP)fA@m 0 j l 6  6R AHA`6PԆ@ -@@mP)jQHĜ|6Ri͆ #ڭ/g!ElQ)Yd= +/ %?wA![ ?*"]r/.,&Քf8ڞ:cLJoB̰G+Y+#IVƆ.3C!nh Oj4jvY?Z.?}I#zv76"NwN6=[t=:\qtAw!qmz8s7%oAmT)/w+ >+:I. -nu#rH`J{A -sv8ېF(+ -ل\ea%'4gwmHik\]?$Ʈ@@m9m=ɖ,6ndϩj^.T2?VWh*`Z*+5^w]Ox0&fv`*f5)PpGjSV1Vww^Pјbzj| _vjWʠzruԉSX<}v˧6TN ]]Ʒ/IJj T.PN-?zȰGYI~Īn= f ݍ%Xzd=gh ۪u٨,ek -2AvzpFGz'RUM:_\\3JOy|UաGt,4ś3}QqiQA>}t$P14'WXlY{^m.B_b`O; ))9:p펆,iwAC{2䯃c]K -ݼxPSIɘUeᓯQ4Q^ z.(ܮNHZB^੠]nk -tZ}:n}*+\IJQWmVmH)tf6*Mk0, |z"샯M1/0r%yV`іQ:Ǩu^IQaghQyĔ2V̭k&j_i^ـt - 7)!S{1zvWT8$ KPcT;Xlbt|sjԾ2rVj tmjB#G{ܵGg9_n%VT> a!+lhˮTF7ͤwaXCiąU78s#+c0o"hj TUazTP,l=#b(fk^`yoP[‚X/׮H|Acj1RuXlA/9Qh# ~{'GmG0'GRGx~eF0$Iv$r1߆wP;B[iƫPuP;3䞖E0؇oP4=/Dm{`ZS - FP;7MAAiu{тպFȋR#y~U؝w8BʒuRoCfff.T QUn̪"JKMv~n+g 5`Dy9 -PJcɀ-tn & WSC](.D`uVBrnE͗T׷jvQXEiM 2f"l9z ~Sꟃl6lk5.wߦQ9~]KDMJ!?xխDePeOn֝׻w~ ^S.<ΡǚI>75:8{df;\3xf~]T3-Rғ}SЌ,˃Ç_̦k3Srݒ)5CGM gz/6f䔅Yw>z!7fSÞ͚Ҭ%!^w e3:-,+kd7r9M\4U#k -V&ϵWrYj>-meTYB dtA4gY < ٵNZNFaS{_p-I.UWQr &jE~6?Gl۠ydP3ry"kAy[ -ʭY =/Q#&Q?|iTmEb@GԼfZyяOmb-6]/:&-pl-I~Ȟ#;fO= PS^1Gߐ'q :"PԖAMY6@٘FdLo6  6R Qo߾ڠLJJJ;w|hBU 2f5wѣrfBcȇ&Z @`6Ps=*2\ ̈́P@>4Ъ@mR !@`6PԆ@ - 6)jHA 0 jC @@mPfA@m  !l 6 @ - 6RjHA`6PA @@mP)fA@m 0 j l 6  6R 6A&l 6 @m 0 jC ~ل¼U3ϐB"vH(43:``Ð@m6N~#^]QY@jAЕ#uHV٩ߣs6 9?rEdq-ڱ:>h&1:xrF?e FEMc-[w 0Z@mP 0Z~Ǡsvf|`C.t_]ȯ4޹簙MrQKӵ@.E''Y {~Z1ϔpؠ1GMzd ͮjyJ1leG|/]]Cc~*ʌ<"g L#G:j)]йWD@.DX4*bsbU^W598"a5JRRR -+pH6tq:8sK#jz'RzX?zi/Ip=t-k]M}wq()A?N-u E{]sH#C r,dfk@|&SPtfvvvfzV pjz">]+J៛%{,r:юm-ü90xo#)$cxF:bqPk2`@"r -/#dMS{/+9GoE!c~iŢs -(h9jq۞*aKȡw4ں6N:#j$LcZm HEj0R@?NZMKU1RcM V"=+;<.>"ebC痝CKm:j]=?Zhg\Vw>1:c6fIG4CNB6a︟| ZNmf#IgweM:(N3_qӿyc fi7hvB/9 S_mF_v1?A([Ȣm~6j^9TM^45l g 9ggkjlY} ΃iD[}hm칕)|:i~b~k5J1z]@~כ_6,Q -d3H̓Z|- smaxjӓu"| -MoY=V7"fE)iXmS{5FjЏFK(:ѻ*#pj -zP{QQK ׆Fߴ82v5XݶBA179?Ҝqoƪ\+T\|Ilz#bmI mX&$ms&!}e vB;|%l/QVy -vo\Og^"jWɄ_I xT-z7=9f&FQ\VNgE{~}栻"bY='"4*(稭2=URP ;n?q{.3ڼè.~F?zkJ#%{[T֟:}*{K؏A;o\" ܚ Q\i -qf?TDk/Sf#SsvteE;ZhZt&7Qi̶ۜQY`8MڍfD 5]|"o+E.JLtA Z0(ܓ.ѺeyrS<هFYI {&m۳j81)+՜O> -ymY5GVzit5ݮsx} -Ǿ~`Ss{k1_[oE{T0︀@ڀ@@3콛u -:Q[cy\v^g ئJMLl@m @mHf?ka_PP8R `l@@cLA -endstream -endobj -104 0 obj -<< -/Length 24814 -/Type /XObject -/Subtype /Image -/Width 655 -/Height 129 -/BitsPerComponent 8 -/ColorSpace /DeviceRGB -/SMask 183 0 R -/Filter /FlateDecode ->> -stream -x\GcLLk4b]QRaEzRD 轈  &z/&Ug1;;3gfnxb԰Wdd$w`X 0[0//Qcm>P0L.9`UnJ?ߑ&_7qzX~-av 4qT`Un-;`=;B Tua= ߴtihh(55L0AXCXa 5L0AXa a L0AXCXa 5L0AXa 5L0AXCX55L0AXa 5L0AXa 55L0AXCXCXa 5L0AXa a L*cTLM&5sf?Z#(V5tHA ԰ -P3 rlEPn H$|,Շw bFb#"|&O800"3fG \n}8.G'uKkۋj99zWa~?KjzZ<] 7Uc}Db౲upܣzWg -Jjw '^Y7_({VM6GsNW? oS pٌ؛[FgEHKkn; 5A#9}-xNM5:*<3J5x˨B{dһLYG&y+*E5奪awJ"ODOrmm)t^S[}h@ze=@X XV+l#[GZցZ*~|0 cF^Qf z&㠲nz]yfcMqT<߽:FX NU)!~1*f>8!WO~R]M Tb &bcypA;E'ޥ1i6ᰕ`'}54A NEkTRA;l]{n@%S?Rw9B$. -9.8X𕒉_ײ(h8ɅYGb%fsL&" f%p Ei1U&>ǣL8-X0`K}C?BYo0k炆' ǩfQNMyo s@$W2 *Dz]M A`hQ6` zY=෯_WC -I"p f!YJK[4xdljCtrotЦj  DzQpu/q8qoZ+ޏ[hDI Qփ &~f6]*X=Ds ` N_F֖i9Ej7btpbAC3H|:ɚ>L:I>~xmmH`~Ørķ/ڗvj_Z;8rLr>E%wY]ᥟkB`_0͵*ZijD >̼d~fenzE'0DaflOKVeif IcU] V/#la2}V/:z*Yu(o˷zEUôܓ)>Qܳ$C(8>qfM_!"#D\^;A,h۪ajvy}:q%ɼ$ V(y紿EP +~pfT6.5lBİY "S&+qOכ`X>P*Nu+ |ՋD+ ݢ[(tB6HWˣbX=2(#rX_߆SLT] z'l,?,&. 4uwكa>+%i@v;Kx+YrX~3.oz7!7SRAs)iI\>aA4חmw*uvJd-^\[gz>΍K-TkŸ:5 axCI4q!Ȏ .8(Fָo k -v{>B!k -mSlCb azڃ;'(- Aϰxcv?jD>@/my5 42VUXɠl?97y);_et*{] 6?sX:eG7\6Yd]K7`&֛1G.daYU4ehok%#8Hwg9 Im&pp-L[e_e GU( ^:A:U4Xz% -wm;6_`{8xg2{w(a=Jz,:b%MZpu6XG>Dx /mn0K\T0#%mdYV& SfzoZ|{!F]+8&=-=%yYL0Cj#G{΁[Zo}F5تmRe#.A iŅKro"nQk%Itz<-(]Zg۱H]Pec/. vgt!p {|7֞Aa-?@F\s7}u޾;Xxr]S;A޽Gdz6[F?i3Lq&Rm8G Nmg@#D$O)$[{ЏF].POe*Kd@C߭$ EHgOIGk&8]jlMјlNZ ɢq B;:wʦWN\pDL=ouʭesAlNiQx6 twv<./*r62RiX^*Œ7 Omgi VW/Y^FSbˁv6)ma-^>F7i:m__y'Զ R -F?؃F6hx(a `a|jYe -2t -љhCZj8]Q_MjV-p _ kM Pcr .USmэdֲlNд 4(cѦ &(]!D*@KbH7FOסCet(h&_ -]&M"A -ɢ ?n_-))CmCܣ;GokM`Ѥf"NR?|? 4 -mZmb5C -\rpaX7-f>gci8&>xX04/?5ÿ5xhXøG}9{ay1 XPVvGBXwXtXPV ^ -aÇPPPPPPPְ ! ! ! ! ?:㖚 -)jo}n5:pṶ*X?HgZ>Hbb8k"bݼG ,j/[@.俸P(D@=UK-vO[H?W-mLh-=jdne?iCN "؞vd+.YvI1$᳂LW{쵳ƥtUf̗/~Io)SMxxK Dۺd[0[4R^nN^~!͑gs9Cb =tegtkCrk ̔+WiZZZzv !?5RK*MMem|yL*./QnQ%œe EfُspW8rok9|!4ak^ǜ _Uy/[;]z_){`fߐP*mvD/N:OWlU=QbXKkR簹bO32s̖Z3 ݻY[tcXg|noCw٭wĚ utӣ{.g}Sg]\g=s7:jelo&<V xb㔍ΦLҴ/>OIEf#kظ88tQuzxRG#qȿbüK&͸,gP\adqLR뱵g-BIi_pvXJ]=\At6NE͍>Ɩp+xXޖ~ᒅM8i}͗)}`ѽ8wmKZ|#3gM89;YD7kg¢s~5؋sY~(p\: Hod>M9ŵ?*ۭzd~=켯uU=Z9ulBF|M*Z:N`.&IMe; :ښ4s'G=s"&p\Oqx" E< o~lң6*X^DPQemGGX/kEJ<&H4æ۬ + ֞U,UzZLy}Zt"&-{_Hy~dl,u2߮r$3/ؤf< [N?k7[E,UZX9{:a0|pQ'͒?\9]?m+k?/[6lq4X:/9M@޷v魈1‰-O:=d$]+pg/8v&q[GŻJb:)J1r4MqDm+P)m DCF@SL|=I{f~4\T18.[a:l,xlsQ험+jf}56azǙEWOL>TwO_M#jK@]sZ , Zꟓu)GV}h) 7wIrsqIͩRH+pky+d |vX򳟛/D(`>J)QIn\j{X3ʲB>i#-rħ ->=Aܫ܏vo.@U ]]]M:9xZ̾[UKc2qORX - -b -^Eo*P2yRwTy[|.[rkvWf"l-YȒM{zzN:g:GϵĮyӿyP$:W u;%Aez{c?_%|=zZfa$Ėæy{XW7v&ڛBgQx3W`w?/󏶤dC98t#x{#&`$g[ԗP/VA@PP8.}Ɋׇl}쯹8KkSAA+Ӹ쩽w6WYxsL-l3TJ -HKp(} ֔ -_ȭ}4 :.?5U]GCY#59c-D%㮶B'L_|t.aEխ?:6UGAH(:gQֵƏV wT:(64˃MYX 37<,"z@]n?-U,lJ/XKª~Of٬}ޛl/?-˞Z D$ދ?;z2uFp?7+aXڐ;/DaIKɠgJ+_M_."DO_ڈR4g!&-J'iSH`5ycՕw+9C"D<yBey.@T؃⋲|:rLza82QI{uuYdw- %}i18M0K'ەg>|XtQRȥ_0ڽ$:j.) >EEɹ5Y[O %9k( -y RK6n= X8jgE%be-uZIZ:lPy 2. _6[R|zèÅk6Gi{ <=}ꤻ<}"lPyPNRHx@wy]uW?sRW@_OM҉b ;ӏ{Sgdb1Tp*؅ ~XFw)SJZ^D}- ] .,-zڼ$H Tm8J'6W,2v9`=iEx|U:Q!,3M~d}K>Q)&l^կ]5vx;!&M)Hkniill|(~.|{_Qu@=(t4քc>Wjlnaɉ+ׯYܠN}Rr9q'1X~YkV4ϟnt, k2#, Nj,+kX_iU4wqnquok$#~¡gx&a|…˯g…y /-6}Ͻ/O¥Zrc^:Fw-([|*@ϟh 4DmڪW!"acQڈ~m{i EQ5*Z3WYAB'e]/|HPv?xTub7OWyk̰7^7pmX?|X˥lVylC_^bd28ʴ05ےQYup -kwd,6}LM˟5xIcni)p8Ϳx]oW*uJ/}٘fy~d= G 1$J[͛lȍ|[߀uK a?/B| jS3 Ua}q;Qߑ%G~!2Be7` ` a O?5 ~9 -wdX]x1 եK@ ~wPP~ ÿC}|48t8 >i0kk((f a YCXCXCAAXCXCAXCAXCAXCAXCAXCXCAAXCAXCXCAXCAXCAXCAXCAXCAXCAXCAXCXCAAXCXCAXCAXCAXCAXCAXCAXCAXCAXCXCAXCAXCXCXCAXCAXCAXCAXCAXCXCAAXCAXCXCAXCAXCAXCAXCAXCAXCAXCXCXCXCAAXCXCH($Ja%CXCAXC}7TUg8ºeITN=>/UEpdBww/.WOo4#!uBǎ,],9G6sa-N?V -_puJO6џ ۔p\]X{&\FG[5ss6^f缙kv7B.&]kNqZU'zxZ$ -2DB>ٸHaKל[9Zvf6}? -E=N6$S(KJr"~HDr3zϝk{,Pɦf͸93^ڠ_╢GJ^ kqݼT9L -ny7T`k+];򃇵Z}=]K| >9Q+75w8ϓо 7_ r -Er!  c8~{2R>4p2BA>M@Qdrc- 8L:/n76A8z -4`]7J]TŠT*U5֠iT -KJܥ i - DmCOr;}Fg=r]}{V5cU})sޒY 2Cdݗx{3]E|92CrH_[鶝Nz4;ƙ v mUۏ;zhjES#ݎyp٦x)( -2n&n)84$*$+Zɓn{vUH=}+줇 cko,hӊl?a,&ɐ,S{ԉv~*s)'{ ɭ8+RnʪKC`fN`uh.$I9GUj'}*?pX cnjjƱ%x^,)!ǧ:Bb$ R(Ǝvm1Hp=Zj[X[YzÃJ X;8VWэAvҋvy#ŏPЃ9%Vޚb3IX[Po5$8Y0١,5At:P:vo}GyxVz-4rÆ5~?o:~1Uڑ([oΚ҈Od0!x׬E7N]ML;0ΙMAtghi8L Q=~1y/~?eNhDG\q';Sg<r^s>@h/SPF#9sҷ#<'yv) uqcGXW_ e8^G50$NoZ[[P5j0w[7ʥܻ֬_Q?fGTeݏp} -ÊYͨ -g-UU{J1c1:cgQ/6Pe\~wc C7C!7M]pDf --fpVI˫iokmim%҉b.ÓO)TxvX$Ri#5{J{ٔ\SizWZƒ3,>@b#Beۂ#/ڕ^^ku@; l0 8<-nSZۀȀ_pB\5gƽaQUYO:$^xޒku=o2 XU<9jt;: ٷyjͼʞ/gZ,qqtݺeT6j ug.U{:Vk>lR0$4W kz>/\г(>_LI gLw>Zׂ޲͟FTJ3& -ɐsh˰FD.l[tJϿ[N8dĮp?酘C;J\|]{>='_PaݐyUEuf,MR Ϧe}2 qO %t]i)XD|&h__PSPPxe)fe X_qn! -8% XWXoFa=EB X{O<6pLbN<{hŤԁfHk.>FҟZ$uaC?? k>~lǏKuϠD/5O3ϽW^ - sTd&&̉;K_9\Z)93ęm˦tk$]·͞<5fǦ2ݿ[i*YY˿^H56[%L wL6\??l$2:ۛ.8O<~*+M^B0')v蟑an8Ud~JS=AY "`[j`_J3Q XUpP{ԟu3ӛ~XԎ.ɻ.Aw?ipls޺Ճpjy)[~@W5ذU,ϋ7uy|@"_H=vAPA>o'Tv@99L|±Z`Ӽ ]t5`ʭO<4Z*ni͋LPi,=(] -EWRXa= -mj^L=k7EyrVA/EUچ$7I}"ӾQBͽꃤtOI1av5Z>,n'28_+|V/;ʀkޕAk0YwyzJM{1~"@(6ۧ=z.\!*a/.lx]J3J3Ԃ~q(ͲݺsqfjoB=~ Y -'0ͨ Rwi5y_?h|Cs06Ny$Yzc) -%^ j*@ago "+ . !1$nVy:7'sןv~ -qvTt_> U7;sӵ_v,faÚ'fKJ݀&չ1~^և|~w+;e? -Y0G_eƨE fLxK0e{kX|Ӧ_~{ y'[ӉRp #@Li) \J#D;̜gd{(rSW|{ִ-Nd@g/{u g WS3?(<߾=Cӌ6tj#8Vݿ8\>b+X0e҆"Ĝ]E ,eƤtؿo۶m*6lI~Kwr3;-Q -*B.z^1wW_=k}4V҂Ӗ;:wusss3+Vvs '9`Z-G'7}m'+?!_Gm):ufT?:m[jko]TBW`_^XP$'` a f0.yw|X]|ڵk=z`=bĈPV~XPVǏo`=;4vqzy̙3T*j -4ܹsX, -w,XEDDzsNHHkPPpYC5k(k(k(k(kk((k(kk(k(k(k(k(k(k(k(kk((kk(k(k(k(k(k(k(k(kk(k(kkk(k(k(k(k(kk((k(kk(k(k(k(k(k(k(kkkk((kk(k(k(k(k(kkXPPPPPPPPPPPPP֯6-B`CXCAXC}(s9p5Nbq@Xl'9,+oQMVWVQo"*62h L¬F2l2k4JzfI! m%f;6Xt}-:*`~,[5B|2X񸑁=}C,kLY=DބQ^扆K &5\&SD~ - ~gYQ\5 zg - 7YԿ}!5"7ו$'?!oG -io]=q.# >jQw}L7h>ybEȡ,21ܿ^JX|"L6yufxz/]O߈Ζ̻7S׵p=]|?}DP_[{ٓn1׳l<&,/%%ia9Ζ벌M$۩X)-<#455T8|>P"oG#|v6kLbXCjRB~Z{ -=|E#U5ɖ+ŋ^[:H0oaݓ%Xfk@C-w9 kn2aI'_;6UQPQgUVDZ+RE\ Ð͒Zā2F2)(a%"2Zk{ƽ<<98JУ>!!B .˹qpywBOyx+j->xi"xdTQ* ֨X݁Fi -N5,)M% ͧcZ䀭(vm[uQtR!b83{+ ~Kդy{/~[;a$SK -N WSWgׯ{ 5U愞`PY>vD[+|}w:[ڻ @H9k܋@$ 3v 'si^;lf k)oT3V+HSb, -:aLөsa"$0+̙&/R\PȨa31tlP܂CﲛH9=0ȵ+mQX -RGA=QYk` '=,ts+ gnK05yn?.]3/*.5"Eģ - XXsXtf x -ޚtvyuBXNRC(ԓF%7Lm\.t0{U4:9{KaV\&p[9rOw3$i C˭4wn'?ggΫZ}4J>I![M73Uϐt -9;hwW5^z%2yͽm=c593.ye4%g$P=.kPŸR*'S˛f,6Mi`Hm;V>^z nAv˼BT)DjǤԩ>RŘ9qЅV.&"f##rgH?wRZ&U;nP|6OHYJ~GWW lk!B&Z&3mĩ&namo $Z7 8ճ5{q66au\p),U!l*n3Q@ھhʘP2 +2r0g,AD^@_n%K举 i]'/âajTum-ry_DDeZMCZ1nWf_o؍+{'rv"1tKk75 U͕9{A _=z<.rږٮ*'q5kJc F 껤E j -X 9kYHuSP+K Hc5.>xݱ1Mg_wZ!`O(y>@ǖޛhM-oi,r;6o4e꪿@."El2:wRӶ-Y*XͯWo.+{ -?j'\Ba :˂0)m -ϒvJZl^>kZBc,۩p5㵚Vm{Lo Jc[Ej6/Ko$ HhZbyvGO)BAX3xna8l%lDuwB)\8|>Iy\.]E)2Pעs}Ga- m@ 1g/ \p\|S|S7N;ȵaBEb@TL|Ao#bϢou%ɲ5Zu'$s*2kyU'Us 6e(iOCC^\#I /ѸHIT kO/zl;̓lx1`ȈPwTD\ogmJq- hڈimnLfVWbh8G9O9V[E>>b]Vp)Swٳ㧮g_%g|GkN,4Ԇdv8 l\錉(*jy%郆n`̕95Пsٳ&܉ك Yw؍+OYalnU]yέ~YZ8qcmz|Ok1sj1$r$%r/oKI -2tX q;+> -x /,*.騌P`.*־$Mo2+t2mgPX2,Syne0VXL:ãԽ5x2r >iΰ ~l^:n:qBN+k9yYnA4qw4]V -nEA7fVTج=%`-f`Laްzo7'#UO#W!9,{pp Χh\wUY!z0B0 $<T[=x@?*SikRoYmPm%쭧)oRAgHT Sx< Ң$+ξGq%E"‘Iz??0?HcYו< @"N]02U4_wG(D Ksu^YJ$"KEv)}&_BfH5)9 -[]^AZSbC6R>WdB#qlKz_5b5griG~Oo(Y帆/l3֐mX <W-UL1c>Rgafru݌)]VO*2ʅS\/j}z4...6&~f>M71aֆ!<|]ҞBF7^Xͤ 6!L ֛S -dEU|\ܕ-"aariEa:XlfDi;xk'< Scb!*?k>+Azɸ&kH>u/'D^}$M\.qXmsA尸"D.qؼ=" -E/er,Sv9t6XA!(XC kH>!=zǏA -qcǎMNN^$VCBB!{z -n -endstream -endobj -105 0 obj -<< -/Length 25982 -/Type /XObject -/Subtype /Image -/Width 661 -/Height 216 -/BitsPerComponent 8 -/ColorSpace /DeviceRGB -/SMask 184 0 R -/Filter /FlateDecode ->> -stream -xXTY];]suBEnEEDIEB%f!}]gf33ss^\nUu5ڙ(T*RX -AT62 >PSQY}e#P;>RX -AT6===݆7oP ~(#m ]ۄ=BAR~ -|h B|C|Cր !!kRHߐߐ5`)$7$7$` $, XB|C|C@K!!!kRHߐߐ5`)$7$7$` $, Xy&/]3qjH?K\xalT)٤#Wy/__`x"79>qE DrQNvցk̿DOW/tr% P\-9 uv壸HģquE}yKI*Pξ:AQX6;ZʂXj[͔ʸ7خC~̜zf%\cD¢WlG^}MdRC*\_!ﲞs?ԫ\Ml&WV#.mqOIsDj1Arr*0D׶q$ WGehD6?;#} *q %l᫘:B,H2LFQ<ݟ]aYL\7Ws -K"#Vls֋.+KD7}슩<<^G&VrPQO+ÐqL*;9&&J%ʼn%nuٵZ@&'xeאj+dW]^|"P4[sb )쐙j뜚PGĐt~}m#Gk/ybiMQOv)[*bweZE'S$7?5[YzjI EzMSG_]}M㊪ P;ᠼ2*G`LW_B)TL/aP畿y%R)r=lXPfd(׾= BBF#)gzfeB!32i[T0+m<:RSTS~%S˶/di"Aret6,H|aIQ?>(-Kr fKHEXJ=GL^qE*E,˴-1D2H۷&eXO*Tb0WS=C18>t)s&(1iTzB^GNΠBQHE+xBl:\٢Ե-5 ]0w>ߚYҬ̌]WY&:Wq<,5&p)cY_sv_Lww=v01>qa,(D-IEum?O]"Ӌ8Xpݧ;<. (x$#O<3 (ܯ.;(8Ӓ~$$Vn^QtTX"SIhwUOo -BcȒSݰl` R"]8֓>|6eȠۭ+W0zyNp%-푏yLn·^Vqvզ-ZIf5[{=G!nD9܌G؈t#ր%KRBE2#qzϷ&SB'c>X\JbLJ&< v#*Eg^ЯZ}H5bBZYR_D~Mޣ[KVO=mKD>cn'n-b -O6R?VT{XtQcz>!3XDkږ1eF6jXZAV{tkOQ{N.!ֵYHkdG|:) B24snF/]h{jq}gc0܄(!Q|}߳J8 -Jq̒5LS(ٯu)-Jڲ7~p-|e -f}3yjM-Xs/;!ݜVE -fqX2[1NPsʠK2ɬ+(mo5}?CbAf|s4 >C5gK*d2t^7h wfoWk2?}[#^~yq7S |Bī?uG?>-!IޮTr6]*REuVW7|TB"t A69j=8#3M7LĈ姞WH̙иVP?m߫*&ؘ̾U? KCe칪z2JAQ|]r,bʸ8+ںdO"-Xаi.E:y%1o*祭O|kpʼn\eKLbD}jdәMyAa8jzl`_&wvfJZmQɗb_2zPƔO:R?^X!r _&~Upږ^W|¢6ٓOpL"Px)ШIMWزǣ(ܪܘSn/jx2ؤ[dU!ŷ3Geh I5Sҽ%1̛^ zC2~ڶqTLr]?*Dו4aE [(U.E"|y[nlPZQDxVylx9],2yW/.UlӞ!UL\ -jwKC_ɥHEvaIX:jU\L,tn5+'KD2q=2FJm5ZpI(1,}]xTbWgv.(?ɹ> 험zGPje߭_WnK(YD[Yx4K"S$yϸl2 S:gߊLdHګ6J*Mz:&HX`QT+Е؅ݏ* -y0[A!Ixaʽ -㐎ydz^\>3PM;/|skz>UWlԘbX'W67DBZԾg8_BdTX!>mZT'F6BRIbZA@|rKOl;ʾ ҦL\%+21MCMˬ*\-z;u@mƋR奡 -D/_x )/C#Qe*P5^nV~/U)csh"ԏ-,6~*ixç+GƣxYzKV%SyD",;{iByuB,>l\\hI -TC_{l[~{@`Hi](7 ^?( qD҆تҽ[>ng#j8T]Q|yfub -_ '+ujwg%G@0;#"˙ĚWFR[~%6aE5 HMٰ}YpYE$eg4ڡ I^\ŇDָ"mY.HǎոL)Hs^8;RV²Mpe[o>zS1C"uir<[e\al-47d>BSflL!r;Q$+ -`\8N \or9N9 7[EaZ!Bç]lծ-DڔLT^\RƓ* ڬuCڪͪzuOJ&X7!a$_v_wȧ%3{G>S.oYI|>+C5ɵ_혤!.5k(P7X/6/ؚŕJ-ecl6yfz8Ů;tK!A׾!AINIA@ ݵkiHу`)Rf̘>PF -P;9RX -ATׯ65 A| ` X -Ao,Ao 7X@oKAo 7 ր kRP{w+t<|5 hSڡj4mz-=W7a|r^zWcˡ9x8n-`]ZTnP]ߢ W`YdX"oB}rƁ;>xjô12`M.>k^cr"TphДT./cĪzP"kuD.q ܼ -4mo7jE^{ҕߗ[hັWt=!"wK ۩N}u+Hv_3|ʱ,RCn:R)SqO\C4V5NS -LyFd>e]fxܮ:TPGŁ״ \9i3>VW/]6׻o]KYPPVȤ|zX@}Yk{&{WܪXk&:cŮ{nIReo#a`7 SFʣǺ`˵:B{͒fUrrDkFqAC]*QV}Ou+̢pOrm_ʄڣ/_g4E'CO=! }J>}1l04W*"PĪͪK9g|V~ߓSEP=t7qsRU34䣨w߳YQ(|#'HSHyA7+adM}!z#s.s7|iӻwqycie췋慠Q5eJJx'&ͱ[N 0yM}>I=_n'K%|vTg\!59Z -OȭwgTHЗ=,5ŲN&W5Ϛij*#ƌl)ـ)*$]lMizZ &%Ůu}fSnT[H2(MtڽpWRs [yWKM{A-҈NX :(f}էd,ҡ3F%S|!_\}WɵOQuyg?ЛavgR'·B86rrzعfs"t"K%4WvYg4ItL!I7| -OB&hsy JZ0{H5_1`{ey<ĄuHLyO|~&K?L"c1u52 ,Mv5D,.IzdY*E|~U -y(S655%Wb1^woQ#aOKRo4{ gzr-&DPƦۭ5œbiú2 %"5͛cS8HBM,&,q*J]ld7lԎ- r#I,=\1qb}FfbYlt^A_bEO:qΔRRi@3FilٶS-rWܳmΝ>u4,[A_[nߺ~37زc)d4_x~c%+/^0{)G L YVG{tC?DuE^frb[DdqoY.PtI#[ %粚)7֑?PW\)R3B8mYG[/ yLG >oeS5c˚I\IE|Mx9L{%A[g+#=u&͟#sHgU9R@ eP__R~ue&?M~lրR& 7hWWMb1P -@ rozzdjj=~P!|h  7 ր kR |5`) |5 A| ` X -| ` ,Ao, 7X 7XKAoKAoo|y^* ` ,829se.ew.LzqjIt({ҰyWjjj.zeJ;tυ-4ݧgAj;|#%rfKo0l Lrc>A%`j+˫*<30pt%zMBƹw< -L<, 5>A!i>EĢkDknmX8aп=雍c7;nh:7) r 9`s&g^EB1k}ȕglǷե7Y&;eQvI;>gTA9XvƍK$$k9I2ј-zt!d29ɱf=L<7x¼Yز= -њL芑;W/~!=pCݲqp&j2S6.Y -۫סGQt/.rpqC/XQquy^9yc?)lV|jxp/w_U;fѓֱy0F\b]╁}_9cϰȤ -[l~wu!EEb@5S*^r. p[xJPܦ~nӐXF{|˯+0MqWh6I TM\ְރF\𻞜gѹ:? as"iќY'^!fڭNjMᖭW:gʨ2rS^g. --!GωʁI BM|)t7=8\@Bn9mG.^5}-=a)20{dRUC|~E={/:v=ᖣķΡV_qPYgo(棌*MsQ39,7 볃Wtoo<ӣ ]?d+Ze̖BAמnP-"Cɍ'2wKeS5xթ"T6}V2Q# 7>9飻- >rDs L*f5Љv|cGs|۠}n|Z彍:oXiѽ;j빅KR]㛏֭d$Y|Zo`0E84{䯃&?%o0yq9Tya[Y]9iv[v#Q ѠߍN[ͫ~ryZ - ;yu":I;hؼSO*Thk̸.rR펍8 ^A=T:l+7$';xČAygiSHĤ8tBjrߒP| l<:d.c;7T.(Cb:}j򾹨e{c_rr 蠆٘Slם-æ|7rE"1u[g CͻS`<2MY@lbޚ߻=皥`Q1t:g`X! U`v͛z>EٍcZV{N{4 kY3hF<6 w\FG־B}.|*Z_/?Tpzo]S6Zo[7ygoBUkߣMBamFS7SY#TtP_\j|Ӌp/*]eqW+־s<wLh*C>d۰3>jo$S(?).qv0|Eb|ΛoJ޿G_jYSY*;pT^.NyA'RxB~].to,%fXeA}>= yi>ωf^gۢm+L5ͬ~|'4Vծ9%hX@'ȫX5l#emRՆdڪBH P(Zf8*wK8PZmo5V/r⦍JD:o$T.%i~wl0`lhzF}qxBm?h8d^c'.<3w>E1a#l.)j ֧m=`#Kv7IΊ߃YߝiO -[':s!("䥝GM^j -`9}q/&Vg]؏ Wdߖ+LƗ猣O+Ts^zV7Jo3'+gˊ\˂÷jUPv;SyαaVG?kfd2;T7M x@CFnȪ-tk!d66wÚV|e rٳ冡՚xÅ7x!\-45gm:cYz -Cae \s_gC˥a9; J΋'Q`sۓ|&ZҭqfLͅk{&y ԺBWKk'Oԯ_3_z|XKŭ*8?;t5ji3QN9vrbsart[ow^i\s})&D8\=_s"-3Lm:'n=uE/Mu&x?@2^b;[ċ,:C2໕Xg{,Bh?aM/n:tԖԋȬ&AQ8·фO|nbQk,X޾Xe - @mOhV 歋.zbZ8ga9,|#8> ?VIKKC0`(G|KA?֫W/݆,)Sڙ=~P5vjh4777Wjg277G|KA?vu݆7 7X@oKA 7 ր 7 րRkR |5`) |5 A| ` X -| ` ,Ao, 7X 7XKAoKA 7 ր -$R<| IDb \ր 7w˽qU~\@btcVlvJSV'#`+N KAo_}Ny_1݈J23;bn]~O_t~U֦nSsW|'RŽ )E-"\3s#0PT9 -%0 r|eb^iٳU ޮZbj?f(c„Oo5KaBe1^Nn/c#z^ % Jz|Cu4X髫1ejn38wZs PWgи*K޺ 2<^x'`D1MȊ pr {SI@Iq =,qJcOu:^*q5F"(ނG}g|fԖʛnwpsfs)jfwq拦~/]8q7l(67%~Н0lefek؀BM6OH"'ÃCoe.47EǔbEǧv=^p r 剥: ,-Ke){J^y3p9.i -Tlr ?yڎM,:9拯bmoYf|BCJRg5(|] |"aÙҐ+Vd贒GS=o))<͹Mv^òXMVno(RUEVD@0Y)؋z9#?9:ԳέaqGtu"b`o:P- -5Y#s"m61 =vi -ow埉ml7/'v>*N ~mش)XV֜ۏ4NS˼T~ vɺ0>}9qi+U/o{ -5Gw^t:ߌΤr}˪4V[b(:zF]sn)Ѷ]Q}iHRvu?9ț}'ߢlC{2"󾸡BR8'!'>D@Q /nlܾ'Ùukdx̤5Y5yΌֹ߈g7[R&|#9l*ƮZ;dIdM藓zX0kX?dW(U3)w^NysVQݦjCQaU̶NwzD9Io-ǬrBR'tf}LAVٗ#$eP{SU PMт[v|j'Z遃7KHZRC-f$90C}6pl:_Cwߍ,Q;Q2v͞L[lF59Z_3^dʡhl>TKWr;# "\ -nULnA3}:lŷY*WB0owd3AEzU y$,RT#g9vGL -.? -M9&_ZZ|Odr9aJc[ʉs7כ:lȨGRp҈7Ӣ/㍿.ն ҆N+ -[xxwfg)GG>wN=Oj |WT`DGE -"XiCP{}wYKB޵"z "͉/Kʙu^ݦ0C77?5jˣN՘lym2p.b:Ȫ} ^:N?ct^W-x -_׿7J5[kLlSM-(ɺaۄwob^4{Bǝ_L*WÌ\ش&'<'_,E 7ɚ5["$2j\}ܣ]VcXs:Sʹ#!;u?X&5~u(pw۱PH5傢cKh()Pn YgҌbL, ^>zf#22IӛkUB\.Qlꄺ'5|L - -Mg{^Qj$_U;-l%$hs+Luԃ$5rDThHe('C(:PD*V57O۬@* SER}fy٩ vN1\>Kۿ&4^ >:cg\*]gصRQ2J⿻0 P9d=Kwi*剿"-c&n5p7l#ՓMDT6v5%o;# W'۲rS >f^4R(CQXpbz:u=r$ܲhZ'p6y \! -_;]5gGe̼ko@@r;"8Ț~_t0_v蟝~6zRxl:{Vr1k\;`e* - Y#a?!N AotוST$swXhǶ̘:im[Hq{B29ּk}\$. 8w{'[:r!Dcg{At1YNGߟ'=z!"f0ѽ`n$<ҿʍCJ߸lO91|1אa-[N\L'JR7L nN^kR|i) Yy58P~ XoKݜu}GԀoP7,} 1_A BMRr2wCAL;RX -AT^z6$=rHMP;R*|h.3"E>THe322m|k@`) |5`)A| ` X -| ` ,Ao, 7X 7XKAoKA 7 ր 7 րRkR |5`) |5 ⛂'3kfl(` X -} |?}_S9ǔfA,= 8drv S V> CP{>bB:X-ݽiZ;w ɁK͡XfeW[P[XeD\#Do’29̖΀vo*K[ ylơ$:Is:{$TL8tuIE$A'-̸JE`YML5:{=ןUZa ^[;̢hw<`,ERr}7P&/Ob-Xx|i?P{_wQludiT^ -KKqY:ZSvCK&uJ]Ϯ[*q5Ob>ߞշ]377qke +[ZesPflLWx3|OJ9T5yCʒW1b >Cv|t@ԱZ˖^yd[ ҉/n홢9_yдQ#-;™ضuQC~|g}9aMjCg\ɧsW֏Si4JQv<[+t޶K_sNCO@}_b_0U!bh'UTX*&g}װYj:/q>y3f0lfc,a]~Wݫl3:Ċ/ -*G(~9Υg06ʈ3$A?]2gU'a`oGV)̧DDg|BγXGs\HMij~ڮﺟbXlXx_Vڮ5QDYY:ܿp匝6h186t=} Q5uƓPO߷Hysܸ{wE=] JQ}?,&"ëGw3uJ.~fn{׏|Gѐ3S՚׍WD&W{5:RӖGE۴] g:y -HƄ[dN -N.tB 'Sq5@wwŵbm([)ڻ~xާh9ϯ1q ҈qW%dx@.,s^\RP %&D}NXpjٖk/q֕ǬÎi:Sb\v%r#GYwFAeQ(3*&>i{8}G}}tg5VLVXiqËG4bnl͖ʚR ]-j5Oڧa~yn7&VUMMmɢ*o>x_BlkUN,Xgs~zi -}c{ -vޠogt B"nYqY=Ad@H_7UR"* k ^W -3o7ow5 ?(s߾}>&M)­l>Քqfdd} 777nC JXg7o k @B -o k }B -7})@o k @B -7 })7o k)@B -o k  }k)@B -o k @B -7 @/wۀll#-f}ɓ3iP)|}6JQ>U ?39赱77~cT_s:"8x5gFsbe77w);PPhynЍQߍ4\:ȹR:?|knb0=~2%p0rM=<!}))&ndbE  D5\KnG5dWɇ4ѢH@n':!ZX\ӭs>?6!p#,#e:'546q_RC_F|L-%_2_x6>sNm:yVVj]OGVwɨNuV;򹍤§nvjk-d%MvGZKT1I|*Ek4h?^?wc]y[vzQFUF6#YA fU&5u 6++}24hiq:[Ů-~rXߺU#u5|6.蠦n\Tu^ -/̲; kh ,cJ3 -}Gkn|c32GH(a{ouŐY,jqrɩ~f(UߴOĔQ* Zw5; =-&:p=&p,?ûsc: EY3@߽N'2}L1Tc:ߞ"9GpTj5yك>5N<;2@W -P!Mq54Mqf5TK'.xyf[Y?V$?ً PFZxq+oU5`RN ,T c%%N~h|<3krjٚ7VƹDrk8{{b{%kul mz޿#vfߍvC{%YAQ׬o =mWXj 8W%N3ZG<葉Ꮂd$ƨ>&%9uU^-]M̈ cE6 L…m"rTH!0lKpnGy1P-軞Zkk4qX?5?6&"˓)ar;<^~ēh9ᇴNR'HOiega[[.WBbMr=?Ğ+1斢7diEV^E~GwZUB}O0{),ztb3"7[~m| [`)v%ةs`P<-QD#/f\+dv0 YKP-軥E%eHni!~cŕuѼ51=@EnDf܀zb5ZTlt -I8SqPFmmB -DZZ=# ->U OF{AP`־LRv-KU2>~|軗Dybj^pWAtӷ>Җ&jEحsU,nf54uYknf=]4tJH"=wz#GÑgs-t77Q3w|C' =SXj,̮^;{c2wX^TW-*yi W)]o{2_7$}w_%kD\}<(}|Fo͛R;`ZW/olJ\Z5׌v,[!mj3ܠ5kVg;oU'<$C}%Ud~>X?rKo;UH\úΧWU9RSFJ; -WHR⃵ >h@$CYȤXޭS"Wm D˟0='$#pwW6,2E3`4?y[9m$;蛑ra"YQ5ջrQfzU3 -Z ~;i͓zh%3|6s(k/sm[!migP,:,Oc܈ +m6WmUZXzH}6 ._[j t-]lw:>H~ooRyFtIEX*oag -OM &-):.syMNZ\aeE )lߥ@''$IؘB<|黁Y",,0 *- -Sh"R1ƾ4ˌ8~? ?Ew_zo[cK޻*kXJWeg~INϭ$1:,^2['*cT.HNH)r:͟9,ZY蘘<"ZY4=9>:>) -ooo Ҿ !Gd2m`-bq ?p5vbB .`h-!̅o_3ۿ*E 5r귳m7f%7"lN&w9؞_\vXJyVR)]MNN=B -p+&ġv5 ֑ -endstream -endobj -106 0 obj -<< -/S /GoTo -/D (cite.USENIX:GKRRS21) ->> -endobj -107 0 obj -<< -/S /GoTo -/D (cite.EPRINT:BGKLRSW21) ->> -endobj -108 0 obj -<< -/F38 168 0 R -/F29 176 0 R -/F50 185 0 R -/F53 186 0 R -/F60 187 0 R -/F51 188 0 R -/F41 189 0 R -/F56 190 0 R -/F44 191 0 R -/F130 177 0 R ->> -endobj -109 0 obj -<< -/S /GoTo -/D (table.1) ->> -endobj -110 0 obj -<< -/S /GoTo -/D (cite.FGHR14) ->> -endobj -111 0 obj -<< -/S /GoTo -/D (table.2) ->> -endobj -112 0 obj -<< -/S /GoTo -/D (table.1) ->> -endobj -113 0 obj -<< -/S /GoTo -/D (table.2) ->> -endobj -114 0 obj -<< -/F38 168 0 R -/F29 176 0 R -/F50 185 0 R -/F53 186 0 R -/F41 189 0 R -/F51 188 0 R -/F60 187 0 R -/F44 191 0 R -/F54 192 0 R -/F133 193 0 R -/F178 194 0 R -/F139 195 0 R -/F154 180 0 R -/F130 177 0 R ->> -endobj -115 0 obj -<< -/S /GoTo -/D (figure.1) ->> -endobj -116 0 obj -<< -/S /GoTo -/D (section.5) ->> -endobj -117 0 obj -<< -/S /GoTo -/D (section.6) ->> -endobj -118 0 obj -<< -/S /GoTo -/D (subsection.3.1) ->> -endobj -119 0 obj -<< -/F50 185 0 R -/F53 186 0 R -/F139 195 0 R -/F41 189 0 R -/F44 191 0 R -/F29 176 0 R -/F38 168 0 R -/F51 188 0 R -/F54 192 0 R -/F56 190 0 R -/F154 180 0 R -/F178 194 0 R ->> -endobj -120 0 obj -<< -/S /GoTo -/D (table.3) ->> -endobj -121 0 obj -<< -/S /GoTo -/D (table.1) ->> -endobj -122 0 obj -<< -/S /GoTo -/D (subsection.3.1) ->> -endobj -123 0 obj -<< -/S /GoTo -/D (subsection.3.2) ->> -endobj -124 0 obj -<< -/S /GoTo -/D (figure.2) ->> -endobj -125 0 obj -<< -/F29 176 0 R -/F41 189 0 R -/F50 185 0 R -/F44 191 0 R -/F139 195 0 R -/F51 188 0 R -/F53 186 0 R -/F56 190 0 R -/F47 196 0 R -/F154 180 0 R -/F60 187 0 R -/F130 177 0 R -/F72 170 0 R -/F79 171 0 R -/F75 172 0 R -/F85 197 0 R -/F212 198 0 R -/F80 199 0 R -/F82 200 0 R -/F38 168 0 R -/F218 201 0 R -/F54 192 0 R ->> -endobj -126 0 obj -<< -/S /GoTo -/D (figure.2) ->> -endobj -127 0 obj -<< -/S /GoTo -/D (equation.5.1) ->> -endobj -128 0 obj -<< -/S /GoTo -/D (table.4) ->> -endobj -129 0 obj -<< -/S /GoTo -/D (table.1) ->> -endobj -130 0 obj -<< -/F38 168 0 R -/F29 176 0 R -/F154 180 0 R -/F50 185 0 R -/F41 189 0 R -/F44 191 0 R -/F53 186 0 R -/F51 188 0 R -/F79 171 0 R -/F80 199 0 R -/F75 172 0 R -/F85 197 0 R -/F83 202 0 R ->> -endobj -131 0 obj -<< -/S /GoTo -/D (figure.3) ->> -endobj -132 0 obj -<< -/S /GoTo -/D (subsection.3.2) ->> -endobj -133 0 obj -<< -/F29 176 0 R -/F38 168 0 R -/F154 180 0 R -/F41 189 0 R -/F50 185 0 R -/F44 191 0 R -/F139 195 0 R -/F178 194 0 R -/F56 190 0 R -/F54 192 0 R -/F53 186 0 R -/F51 188 0 R -/F47 196 0 R -/F72 170 0 R -/F79 171 0 R -/F75 172 0 R -/F85 197 0 R -/F212 198 0 R -/F83 202 0 R -/F80 199 0 R ->> -endobj -134 0 obj -<< -/S /GoTo -/D (section.5) ->> -endobj -135 0 obj -<< -/S /GoTo -/D (figure.4) ->> -endobj -136 0 obj -<< -/S /GoTo -/D (figure.4) ->> -endobj -137 0 obj -<< -/S /GoTo -/D (figure.3) ->> -endobj -138 0 obj -<< -/F29 176 0 R -/F56 190 0 R -/F53 186 0 R -/F41 189 0 R -/F50 185 0 R -/F51 188 0 R -/F44 191 0 R -/F139 195 0 R -/F38 168 0 R -/F54 192 0 R -/F154 180 0 R -/F178 194 0 R ->> -endobj -139 0 obj -<< -/S /GoTo -/D (table.5) ->> -endobj -140 0 obj -<< -/S /GoTo -/D (table.2) ->> -endobj -141 0 obj -<< -/F29 176 0 R -/F44 191 0 R -/F130 177 0 R -/F178 194 0 R -/F257 203 0 R ->> -endobj -142 0 obj -<< -/Type /Action -/S /URI -/URI (https://eprint.iacr.org/2021/1038) ->> -endobj -143 0 obj -<< -/Type /Action -/S /URI -/URI (https://eprint.iacr.org/2020/1143) ->> -endobj -144 0 obj -<< -/F38 168 0 R -/F29 176 0 R -/F41 189 0 R -/F44 191 0 R -/F130 177 0 R ->> -endobj -145 0 obj -<< -/Type /Font -/Subtype /Type0 -/BaseFont /XOUOBL+LMRoman12-Bold-Identity-H -/Encoding /Identity-H -/DescendantFonts [204 0 R] -/ToUnicode 205 0 R ->> -endobj -146 0 obj -<< -/Type /Font -/Subtype /Type0 -/BaseFont /EHICPI+LMRoman10-Regular-Identity-H -/Encoding /Identity-H -/DescendantFonts [206 0 R] -/ToUnicode 207 0 R ->> -endobj -147 0 obj -<< -/Type /Font -/Subtype /Type0 -/BaseFont /SHWLBR+LMRoman10-Bold-Identity-H -/Encoding /Identity-H -/DescendantFonts [208 0 R] -/ToUnicode 209 0 R ->> -endobj -148 0 obj -<< -/Type /Font -/Subtype /Type0 -/BaseFont /VUPEFF+LMRoman12-Regular-Identity-H -/Encoding /Identity-H -/DescendantFonts [210 0 R] -/ToUnicode 211 0 R ->> -endobj -149 0 obj -<< -/Length 989 -/Type /Metadata -/Subtype /XML ->> -stream - logo-episcience-tampon -endstream -endobj -150 0 obj -<< -/Length 995 -/N 3 -/Filter /FlateDecode ->> -stream -xڍUoT?o\?US[IB*unS6mUo xB ISA$=t@hpS]Ƹ9w>5@WI`]5;V! A'@{N\..ƅG_!7suV$BlW=}i; F)A<.&Xax,38S(b׵*%31l #O-zQvaXOP5o6Zz&⻏^wkI/#&\%x/@{7S މjPh͔&mry>k7=ߪB#@fs_{덱п0-LZ~%Gpˈ{YXf^+_s-T>D@קƸ-9!r[2]3BcnCs?>*ԮeD|%4` :X2 pQSLPRaeyqĘ י5Fit )CdL$o$rpӶb>4+̹F_{Яki+x.+B.{L<۩ -=UHcnf<>F ^e||pyv%b:iX'%8Iߔ? rw[vITVQN^dpYI"|#\cz[2M^S0[zIJ/HHȟ- Ic> -stream -x1 /Rx d -endstream -endobj -152 0 obj -<< -/BitsPerComponent 8 -/Colors 3 -/Columns 229 -/Predictor 15 ->> -endobj -153 0 obj -<< -/Names [(Doc-Start) 213 0 R (Item.1) 214 0 R (Item.2) 215 0 R (Item.3) 216 0 R (appendix.A) 217 0 R -(cite.AC:AGRRT16) 218 0 R] -/Limits [(Doc-Start) (cite.AC:AGRRT16)] ->> -endobj -154 0 obj -<< -/Names [(cite.EPRINT:BGKLRSW21) 219 0 R (cite.EPRINT:SzeAshDho20) 220 0 R (cite.FGHR14) 221 0 R (cite.USENIX:GKRRS21) 222 0 R (definition.1) 223 0 R -(equation.5.1) 224 0 R] -/Limits [(cite.EPRINT:BGKLRSW21) (equation.5.1)] ->> -endobj -155 0 obj -<< -/Names [(equation.6.2) 225 0 R (equation.6.3) 226 0 R (figure.1) 227 0 R (figure.2) 228 0 R (figure.3) 229 0 R -(figure.4) 230 0 R] -/Limits [(equation.6.2) (figure.4)] ->> -endobj -156 0 obj -<< -/Names [(page.1) 231 0 R (page.10) 232 0 R (page.11) 233 0 R (page.2) 234 0 R (page.3) 235 0 R -(page.4) 236 0 R] -/Limits [(page.1) (page.4)] ->> -endobj -157 0 obj -<< -/Names [(page.5) 237 0 R (page.6) 238 0 R (page.7) 239 0 R (page.8) 240 0 R (page.9) 241 0 R -(section*.1) 242 0 R] -/Limits [(page.5) (section*.1)] ->> -endobj -158 0 obj -<< -/Names [(section*.2) 243 0 R (section*.3) 244 0 R (section*.4) 245 0 R (section*.5) 246 0 R (section*.6) 247 0 R -(section.1) 248 0 R] -/Limits [(section*.2) (section.1)] ->> -endobj -159 0 obj -<< -/Names [(section.2) 249 0 R (section.3) 250 0 R (section.4) 251 0 R (section.5) 252 0 R (section.6) 253 0 R -(subsection.2.1) 254 0 R] -/Limits [(section.2) (subsection.2.1)] ->> -endobj -160 0 obj -<< -/Names [(subsection.2.2) 255 0 R (subsection.3.1) 256 0 R (subsection.3.2) 257 0 R (table.1) 258 0 R (table.2) 259 0 R -(table.3) 260 0 R] -/Limits [(subsection.2.2) (table.3)] ->> -endobj -161 0 obj -<< -/Names [(table.4) 261 0 R (table.5) 262 0 R] -/Limits [(table.4) (table.5)] ->> -endobj -162 0 obj -<< -/S /GoTo -/D (section.2) ->> -endobj -163 0 obj -<< -/Title (Algebraic modelization of the challenges) -/A 263 0 R -/Parent 5 0 R -/Prev 90 0 R -/Next 264 0 R -/First 265 0 R -/Last 266 0 R -/Count -2 ->> -endobj -164 0 obj -<< -/Title (Univariate case) -/A 267 0 R -/Parent 90 0 R -/Next 165 0 R ->> -endobj -165 0 obj -<< -/Title (Multivariate case) -/A 268 0 R -/Parent 90 0 R -/Prev 164 0 R ->> -endobj -166 0 obj -<< -/S /GoTo -/D (section.6) ->> -endobj -167 0 obj -<< -/Title (Attacks Against Round-Reduced Poseidon) -/A 269 0 R -/Parent 5 0 R -/Prev 264 0 R -/Next 92 0 R ->> -endobj -168 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /GFBJLA+LMSans10-Bold -/FontDescriptor 270 0 R -/FirstChar 45 -/LastChar 122 -/Widths [366.7 305.5 550 550 550 550 550 550 550 550 -550 550 550 305.5 305.5 894.4 855.6 894.4 519.5 733.3 -733.3 733.3 702.8 794.5 641.7 611.1 733.3 794.5 330.5 519.5 -763.9 580.5 977.8 794.5 794.5 702.8 794.5 702.8 611.1 733.3 -763.9 733.3 1038.9 733.3 733.3 672.2 343.1 575 343.1 555.5 -733.3 305.5 525 561.1 488.9 561.1 511.1 336.1 550 561.1 -255.5 286.1 530.6 255.5 866.7 561.1 550 561.1 561.1 372.2 -421.7 404.2 561.1 500 744.5 500 500 476.4] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -169 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /YSECYB+LMRoman12-Regular -/FontDescriptor 273 0 R -/FirstChar 44 -/LastChar 235 -/Widths [272 326.4 272 489.6 489.6 489.6 489.6 489.6 489.6 489.6 -489.6 489.6 489.6 489.6 272 272 761.6 761.6 761.6 462.4 -761.6 734 693.4 707.2 747.8 666.2 639 768.2 734 353.2 -503 761.2 611.8 897.2 734 761.6 666.2 761.6 720.6 544 -707.2 734 734 1006 734 734 598.4 272 500 272 -555.6 734 272 489.6 544 435.2 544 435.2 299.2 489.6 -544 272 299.2 516.8 272 816 544 489.6 544 516.8 -380.8 386.2 380.8 544 516.8 707.2 516.8 516.8 435.2 500 -277.8 500 555.6 163.2 734 734 707.2 707.2 747.8 666.2 -666.2 768.2 611.8 611.8 611.8 734 734 734 761.6 720.6 -720.6 544 544 544 707.2 707.2 734 734 734 598.4 -598.4 598.4 820.9 353.2 544 474.6 489.6 489.6 435.2 435.2 -544 435.2 435.2 489.6 272 272 329.1 544 544 494.5 -489.6 380.8 380.8 386.2 386.2 386.2 380.8 380.8 544 544 -516.8 435.2 435.2 435.2 544 272 462.4 734 734 734 -734 734 734 734 883.7 707.2 666.2 666.2 666.2 666.2 -353.2 353.2 353.2 353.2 747.8 734 761.6 761.6 761.6 761.6 -761.6 992.6 761.6 734 734 734 734 734 611.8 1088 -489.6 489.6 489.6 489.6 489.6 489.6 707.2 435.2 435.2 435.2 -435.2 435.2] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -170 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /DKDJEQ+LMRoman8-Regular -/FontDescriptor 274 0 R -/FirstChar 40 -/LastChar 63 -/Widths [413.2 413.2 531.3 826.4 295.1 354.2 295.1 531.3 531.3 531.3 -531.3 531.3 531.3 531.3 531.3 531.3 531.3 531.3 295.1 295.1 -295.1 826.4 501.8 501.8] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -171 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /TYQWSU+LMMathItalic8-Regular -/FontDescriptor 277 0 R -/FirstChar 59 -/LastChar 103 -/Widths [295.1 826.4 531.3 826.4 531.3 559.7 795.8 801.4 757.3 871.7 -778.7 672.4 827.9 872.8 460.7 580.4 896 722.6 1020.4 843.3 -806.2 673.6 835.7 800.2 646.2 618.6 718.8 618.8 1002.4 873.9 -615.8 720 413.2 413.2 413.2 1062.5 1062.5 434 564.4 454.5 -460.2 546.7 492.9 510.4 505.6] -/Encoding 278 0 R -/ToUnicode 279 0 R ->> -endobj -172 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /XKGWFN+LMRoman6-Regular -/FontDescriptor 280 0 R -/FirstChar 43 -/LastChar 51 -/Widths [935.2 351.8 416.7 351.8 611.1 611.1 611.1 611.1 611.1] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -173 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /INLLGP+LMRoman9-Regular -/FontDescriptor 281 0 R -/FirstChar 27 -/LastChar 122 -/Widths [599.6 571 571 856.4 856.4 513.9 285.5 387.5 856.4 513.9 -856.4 799.4 285.5 399.7 399.7 513.9 799.4 285.5 342.6 285.5 -513.9 513.9 513.9 513.9 513.9 513.9 513.9 513.9 513.9 513.9 -513.9 285.5 285.5 799.4 799.4 799.4 485.3 799.4 770.7 727.9 -742.3 785 699.4 670.8 806.5 770.7 371 528.1 799.2 642.3 -942 770.7 799.4 699.4 799.4 756.4 571 742.3 770.7 770.7 -1056.1 770.7 770.7 628.1 285.5 513.9 285.5 555.6 770.7 285.5 -513.9 571 456.8 571 457.2 314 513.9 571 285.5 314 -542.4 285.5 856.4 571 513.9 571 542.4 402 405.4 399.7 -571 542.4 742.3 542.4 542.4 456.8] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -174 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /ESBLLB+LMSans9-Regular -/FontDescriptor 282 0 R -/FirstChar 45 -/LastChar 117 -/Widths [342.6 285.5 513.9 513.9 513.9 513.9 513.9 513.9 513.9 513.9 -513.9 513.9 513.9 285.5 285.5 799.4 799.4 799.4 485.3 685.2 -686.7 685.9 656.7 743.1 617.3 588.7 685.2 726.8 287 486.1 -715.3 560.2 898.1 726.8 759.2 657.4 759.2 665.9 571 702.2 -706.8 686.7 972.2 686.7 686.7 628.1 298.6 513.9 298.6 555.6 -686.7 285.5 493.8 530.8 456.8 530.8 456.8 314 513.9 530.8 -245.4 273.9 502.3 245.4 816.3 530.8 513.9 530.8 530.8 351.1 -394 371.1 530.8] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -175 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /KFFWBM+LMMono9-Regular -/FontDescriptor 283 0 R -/FirstChar 77 -/LastChar 116 -/Widths [525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -176 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /CWCKIB+LMRoman10-Regular -/FontDescriptor 284 0 R -/FirstChar 21 -/LastChar 252 -/Widths [500 1000 0 391.7 277.8 305.6 583.3 555.6 555.6 833.3 -833.3 500 277.8 373.8 833.3 500 833.3 777.8 277.8 388.9 -388.9 500 777.8 277.8 333.3 277.8 500 500 500 500 -500 500 500 500 500 500 500 277.8 277.8 777.8 -777.8 777.8 472.2 777.8 750 708.3 722.2 763.9 680.6 652.8 -784.7 750 361.1 513.9 777.8 625 916.7 750 777.8 680.6 -777.8 736.1 555.6 722.2 750 750 1027.8 750 750 611.1 -277.8 500 277.8 555.6 750 277.8 500 555.6 444.5 555.6 -444.5 305.6 500 555.6 277.8 305.6 527.8 277.8 833.3 555.6 -500 555.6 527.8 391.7 394.5 388.9 555.6 527.8 722.2 527.8 -527.8 444.5 500 277.8 500 555.6 166.7 750 750 722.2 -722.2 763.9 680.6 680.6 784.7 625 625 625 750 750 -750 777.8 736.1 736.1 555.6 555.6 555.6 722.2 722.2 750 -750 750 611.1 611.1 611.1 838.9 361.1 555.6 484 500 -500 444.5 444.5 555.6 444.5 444.5 500 277.8 277.8 336.1 -555.6 555.6 506.3 500 391.7 391.7 394.5 394.5 394.5 388.9 -388.9 555.6 555.6 527.8 444.5 444.5 444.5 555.6 277.8 472.2 -750 750 750 750 750 750 750 902.8 722.2 680.6 -680.6 680.6 680.6 361.1 361.1 361.1 361.1 763.9 750 777.8 -777.8 777.8 777.8 777.8 1013.9 777.8 750 750 750 750 -750 625 1111.1 500 500 500 500 500 500 722.2 -444.5 444.5 444.5 444.5 444.5 277.8 277.8 277.8 277.8 500 -555.6 500 500 500 500 500 777.8 500 555.6 555.6 -555.6 555.6] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -177 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /XCHFVP+LMMono10-Regular -/FontDescriptor 285 0 R -/FirstChar 33 -/LastChar 122 -/Widths [525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525 -525 525 525 525 525 525 525 525 525 525] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -178 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /XPKLFT+LMRoman7-Bold -/FontDescriptor 286 0 R -/FirstChar 43 -/LastChar 43 -/Widths [1002.4] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -179 0 obj -<< -/Length 160 -/Type /XObject -/Subtype /Image -/Width 659 -/Height 215 -/BitsPerComponent 8 -/ColorSpace /DeviceGray -/Filter /FlateDecode ->> -stream -x ]UpR9k -endstream -endobj -180 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /DTWNCY+LMSans10-Regular -/FontDescriptor 287 0 R -/FirstChar 45 -/LastChar 117 -/Widths [333.3 277.8 500 500 500 500 500 500 500 500 -500 500 500 277.8 277.8 777.8 777.8 777.8 472.2 666.7 -666.7 666.7 638.9 722.2 597.2 569.5 666.7 708.3 277.8 472.2 -694.5 541.7 875 708.3 736.1 638.9 736.1 645.8 555.6 680.6 -687.5 666.7 944.4 666.7 666.7 611.1 288.9 500 288.9 555.6 -666.7 277.8 480.6 516.7 444.5 516.7 444.5 305.6 500 516.7 -238.9 266.7 488.9 238.9 794.5 516.7 500 516.7 516.7 341.7 -383.3 361.1 516.7] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -181 0 obj -<< -/Length 160 -/Type /XObject -/Subtype /Image -/Width 659 -/Height 215 -/BitsPerComponent 8 -/ColorSpace /DeviceGray -/Filter /FlateDecode ->> -stream -x ]UpR9k -endstream -endobj -182 0 obj -<< -/Length 160 -/Type /XObject -/Subtype /Image -/Width 658 -/Height 215 -/BitsPerComponent 8 -/ColorSpace /DeviceGray -/Filter /FlateDecode ->> -stream -x1 g -?Ej -endstream -endobj -183 0 obj -<< -/Length 105 -/Type /XObject -/Subtype /Image -/Width 655 -/Height 129 -/BitsPerComponent 8 -/ColorSpace /DeviceGray -/Filter /FlateDecode ->> -stream -x1 g * -endstream -endobj -184 0 obj -<< -/Length 161 -/Type /XObject -/Subtype /Image -/Width 661 -/Height 216 -/BitsPerComponent 8 -/ColorSpace /DeviceGray -/Filter /FlateDecode ->> -stream -x1 g /xPت -endstream -endobj -185 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /TRLRDJ+LMMathItalic10-Regular -/FontDescriptor 288 0 R -/FirstChar 11 -/LastChar 121 -/Widths [639.7 565.6 517.7 444.4 405.9 437.5 496.5 469.4 353.9 576.2 -583.3 602.6 494 437.5 570 517 571.4 437.2 540.3 595.8 -625.7 651.4 622.5 466.3 591.4 828.1 517 362.8 654.2 1000 -1000 1000 1000 277.8 277.8 500 500 500 500 500 -500 500 500 500 500 500 500 277.8 277.8 777.8 -500 777.8 500 530.9 750 758.5 714.7 827.9 738.2 643.1 -786.3 831.3 439.6 554.5 849.3 680.6 970.1 803.5 762.8 642 -790.6 759.3 613.2 584.4 682.8 583.3 944.4 828.5 580.6 682.6 -388.9 388.9 388.9 1000 1000 416.7 528.6 429.2 432.8 520.5 -465.6 489.6 477 576.2 344.5 411.8 520.6 298.4 878 600.2 -484.7 503.1 446.4 451.2 468.8 361.1 572.5 484.7 715.9 571.5 -490.3] -/Encoding 278 0 R -/ToUnicode 279 0 R ->> -endobj -186 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /OAHGDH+LMMathSymbols10-Regular -/FontDescriptor 289 0 R -/FirstChar 0 -/LastChar 103 -/Widths [777.8 277.8 777.8 500 777.8 500 777.8 777.8 777.8 777.8 -777.8 777.8 777.8 1000 500 500 777.8 777.8 777.8 777.8 -777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 1000 1000 -777.8 777.8 1000 1000 500 500 1000 1000 1000 777.8 -1000 1000 611.1 611.1 1000 1000 1000 777.8 275 1000 -666.7 666.7 888.9 888.9 0 0 555.6 555.6 666.7 500 -722.2 722.2 777.8 777.8 611.1 798.5 656.8 526.5 771.4 527.8 -718.7 594.9 844.5 544.5 677.8 762 689.7 1200.9 820.5 796.1 -695.6 816.7 847.5 605.6 544.6 625.8 612.8 987.8 713.3 668.3 -724.7 666.7 666.7 666.7 666.7 666.7 611.1 611.1 444.4 444.4 -444.4 444.4 500 500] -/Encoding 290 0 R -/ToUnicode 291 0 R ->> -endobj -187 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /EDGZDU+MSBM10 -/FontDescriptor 292 0 R -/FirstChar 70 -/LastChar 70 -/Widths [611.1] ->> -endobj -188 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /LJRDHD+LMMathItalic7-Regular -/FontDescriptor 293 0 R -/FirstChar 11 -/LastChar 117 -/Widths [742.7 647.8 600.1 519.3 476.1 519.8 588.6 544.1 422.8 668.8 -677.6 694.6 572.8 519.8 668 592.7 662 526.8 632.9 686.9 -713.8 756 719.7 539.7 689.9 950 592.7 439.2 751.4 1138.9 -1138.9 1138.9 1138.9 339.3 339.3 585.3 585.3 585.3 585.3 585.3 -585.3 585.3 585.3 585.3 585.3 585.3 585.3 339.3 339.3 892.9 -585.3 892.9 585.3 610.1 859.1 863.2 819.4 934.1 838.7 724.5 -889.4 935.6 506.3 632 959.9 783.7 1089.4 904.9 868.9 727.3 -899.7 860.6 701.5 674.8 778.2 674.6 1074.4 936.9 671.5 778.4 -462.3 462.3 462.3 1138.9 1138.9 478.2 619.7 502.4 510.5 594.7 -542 557.1 557.3 668.8 404.2 472.7 607.3 361.3 1013.7 706.2 -563.9 588.9 523.6 530.4 539.2 431.6 675.4] -/Encoding 278 0 R -/ToUnicode 279 0 R ->> -endobj -189 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /CWCKIB+LMRoman10-Regular -/FontDescriptor 284 0 R -/FirstChar 40 -/LastChar 114 -/Widths [388.9 388.9 500 777.8 277.8 333.3 277.8 500 500 500 -500 500 500 500 500 500 500 500 277.8 277.8 -277.8 777.8 472.2 472.2 777.8 750 708.3 722.2 763.9 680.6 -652.8 784.7 750 361.1 513.9 777.8 625 916.7 750 777.8 -680.6 777.8 736.1 555.6 722.2 750 750 1027.8 750 750 -611.1 277.8 472 277.8 500 277.8 277.8 500 555.6 444.5 -555.6 444.5 305.6 500 555.6 277.8 305.6 527.8 277.8 833.3 -555.6 500 555.6 527.8 391.7] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -190 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /XXFXYN+LMMathExtension10-Regular -/FontDescriptor 294 0 R -/FirstChar 0 -/LastChar 65 -/Widths [458.3 458.3 416.7 416.7 472.2 472.2 472.2 472.2 583.3 583.3 -472.2 472.2 333.3 555.6 577.8 577.8 597.2 597.2 736.1 736.1 -527.8 527.8 583.3 583.3 583.3 583.3 750 750 750 750 -1044.4 1044.4 791.7 791.7 583.3 583.3 638.9 638.9 638.9 638.9 -805.6 805.6 805.6 805.6 1277.8 1277.8 811.1 811.1 875 875 -666.7 666.7 666.7 666.7 666.7 666.7 888.9 888.9 888.9 888.9 -888.9 888.9 888.9 666.7 875 875] -/Encoding 295 0 R -/ToUnicode 296 0 R ->> -endobj -191 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /UJZBQP+LMRoman7-Regular -/FontDescriptor 297 0 R -/FirstChar 40 -/LastChar 57 -/Widths [446.4 446.4 569.4 877 323.4 384.9 323.4 569.4 569.4 569.4 -569.4 569.4 569.4 569.4 569.4 569.4 569.4 569.4] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -192 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /CIXFWA+LMMathSymbols7-Regular -/FontDescriptor 298 0 R -/FirstChar 0 -/LastChar 0 -/Widths [892.9] -/Encoding 290 0 R -/ToUnicode 291 0 R ->> -endobj -193 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /PEZVRG+LMRoman10-Bold -/FontDescriptor 299 0 R -/FirstChar 28 -/LastChar 116 -/Widths [638.9 638.9 958.3 958.3 575 350 481.5 958.3 575 958.3 -894.4 319.5 447.2 447.2 575 894.4 319.5 383.3 319.5 575 -575 575 575 575 575 575 575 575 575 575 -319.5 319.5 894.4 894.4 894.4 543.1 894.4 869.4 818.1 830.6 -881.9 755.6 723.6 904.2 900 436.1 594.5 901.4 691.7 1091.7 -900 863.9 786.1 863.9 862.5 638.9 800 884.7 869.4 1188.9 -869.4 869.4 702.8 319.5 575 319.5 555.6 869.4 319.5 559 -638.9 511.1 638.9 527.1 351.4 575 638.9 319.5 351.4 607 -319.5 958.3 638.9 575 638.9 607 473.6 453.6 447.2] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -194 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /MHVERS+LMRoman10-Italic -/FontDescriptor 300 0 R -/FirstChar 44 -/LastChar 121 -/Widths [306.7 357.8 306.7 511.1 511.1 511.1 511.1 511.1 511.1 511.1 -511.1 511.1 511.1 511.1 306.7 306.7 777.8 766.7 777.8 511.1 -766.7 743.4 703.9 715.6 755 678.4 652.8 773.6 743.4 385.5 -525 768.9 627.2 896.7 743.4 766.7 678.4 766.7 729.5 562.2 -715.6 743.4 743.4 998.9 743.4 743.4 613.3 306.7 500 306.7 -555.6 743.4 306.7 511.1 460 460 511.1 460 306.7 460 -511.1 306.7 306.7 460 255.5 817.8 562.2 511.1 511.1 460 -421.7 408.9 332.2 536.7 460 664.5 463.9 485.6] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -195 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /PEZVRG+LMRoman10-Bold -/FontDescriptor 299 0 R -/FirstChar 88 -/LastChar 89 -/Widths [869.4 869.4] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -196 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /PSSDWM+LMRoman5-Regular -/FontDescriptor 301 0 R -/FirstChar 48 -/LastChar 50 -/Widths [680.6 680.6 680.6] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -197 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /EXIYFS+MSAM10 -/FontDescriptor 302 0 R -/FirstChar 1 -/LastChar 1 -/Widths [777.8] ->> -endobj -198 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /CKFRNU+LMRoman8-Bold -/FontDescriptor 303 0 R -/FirstChar 88 -/LastChar 89 -/Widths [922.3 922.3] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -199 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /MCLQZE+LMMathItalic6-Regular -/FontDescriptor 304 0 R -/FirstChar 61 -/LastChar 105 -/Widths [638.9 963 638.9 658.7 924.1 926.6 883.7 998.3 899.8 775 -952.9 999.5 547.7 681.6 1025.7 846.3 1161.6 967.1 934.1 780 -966.5 922.1 756.7 731.1 838.1 729.6 1150.9 1001.4 726.4 837.7 -509.3 509.3 509.3 1222.2 1222.2 518.5 674.9 547.7 559.1 642.5 -589 600.7 607.7 725.7 445.6] -/Encoding 278 0 R -/ToUnicode 279 0 R ->> -endobj -200 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /ANTVSN+LMMathSymbols8-Regular -/FontDescriptor 305 0 R -/FirstChar 0 -/LastChar 0 -/Widths [826.4] -/Encoding 290 0 R -/ToUnicode 291 0 R ->> -endobj -201 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /DTWNCY+LMSans10-Regular -/FontDescriptor 287 0 R -/FirstChar 70 -/LastChar 82 -/Widths [569.5 666.7 708.3 277.8 472.2 694.5 541.7 875 708.3 736.1 -638.9 736.1 645.8] -/Encoding 275 0 R -/ToUnicode 276 0 R ->> -endobj -202 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /ONAHZY+LMMathSymbols6-Regular -/FontDescriptor 306 0 R -/FirstChar 0 -/LastChar 0 -/Widths [963] -/Encoding 290 0 R -/ToUnicode 291 0 R ->> -endobj -203 0 obj -<< -/Type /Font -/Subtype /Type1 -/BaseFont /PUUKUE+LMRomanCaps10-Regular -/FontDescriptor 307 0 R -/FirstChar 80 -/LastChar 115 -/Widths [741.7 844.4 800 611.1 786.1 813.9 813.9 1105.5 813.9 813.9 -669.5 319.4 500 319.4 555.6 813.9 319.4 613.3 580 591.1 -624.5 557.8 535.6 641.1 613.3 302.2 424.5 635.6 513.3 746.7 -613.3 635.6 557.8 635.6 602.2 457.8] -/Encoding 271 0 R -/ToUnicode 272 0 R ->> -endobj -204 0 obj -<< -/Type /Font -/Subtype /CIDFontType0 -/BaseFont /XOUOBL+LMRoman12-Bold -/CIDSystemInfo 308 0 R -/FontDescriptor 309 0 R -/DW 280 -/W [27 [850 547] - 35 [625] - 43 [500 313] - 47 [625] - 50 [513 563] -54 [707 344 563] - 59 [563] - 62 [880 625 375 419 313] - 70 [594 676 313] - 75 [938] -77 [625] - 81 [563 563 769 625] - 88 [313] - 96 [460] - 98 [444] -100 [563] - 102 [563] - 104 [782 438 563] - 109 [625] - 112 [594] -120 [500 563] -] ->> -endobj -205 0 obj -<< -/Length 407 -/Filter /FlateDecode ->> -stream -x]k0+1hAZB]Ǻ^^;FICi~]Ko["tmy^k[9wW8ѥA,EU=Ma+!mjrhGGB2oǭfcϭ!¯AֻxW^ǵ"^]wl/ ˼\ʶ[W -{` O&fտ}5E\n Cx%q)AR$Z5OJRTALSDr -y~$$ z{ங؝5z BR6@'  b]> -endobj -207 0 obj -<< -/Length 499 -/Filter /FlateDecode ->> -stream -x]j0l)ŒeI ְ-MZ6s_[gB |̜Hq^g=ؙzy{hϽDºo;4S,_uV?~} n?~TBΞ6YvPvxI/qw>CrNӷ@yԴ7l_=U˳~iS/ q -PH$#R/-eȓ (h"P*@zZ'7 =5`%jGDD%b'R2r ʈPYP$RTi"0 ]RpEAO=Ai Cv> -endobj -209 0 obj -<< -/Length 255 -/Filter /FlateDecode ->> -stream -x]P0+ޱR4m݃mY]> -endobj -211 0 obj -<< -/Length 346 -/Filter /FlateDecode ->> -stream -x]Qk0w?E7FD7 -ºCԇ~]I檛{2CUĚNa6r`uWMVR;nr,WsC}C_*.6gh{i6K%CyZ< \5"1wudeW7x]}LZrM -|SZߡ51ωcQRDr,ʖ+v@ƅO[Sf-$ Ac#$I%O9%\H9R`+?H!U,;Y\J夌䣶JzAz!zYhΐvڜDeɽ#g%&fc_ͰNr -endstream -endobj -212 0 obj -<< -/BitsPerComponent 8 -/Colors 1 -/Columns 229 -/Predictor 2 ->> -endobj -213 0 obj -<< -/D [8 0 R /XYZ 104.175 736.654 null] ->> -endobj -214 0 obj -<< -/D [10 0 R /XYZ 104.175 653.324 null] ->> -endobj -215 0 obj -<< -/D [10 0 R /XYZ 104.175 611.248 null] ->> -endobj -216 0 obj -<< -/D [10 0 R /XYZ 104.175 555.113 null] ->> -endobj -217 0 obj -<< -/D [18 0 R /XYZ 104.175 736.654 null] ->> -endobj -218 0 obj -<< -/D [16 0 R /XYZ 104.175 165.012 null] ->> -endobj -219 0 obj -<< -/D [17 0 R /XYZ 104.175 738.646 null] ->> -endobj -220 0 obj -<< -/D [17 0 R /XYZ 104.175 559.319 null] ->> -endobj -221 0 obj -<< -/D [17 0 R /XYZ 104.175 670.9 null] ->> -endobj -222 0 obj -<< -/D [17 0 R /XYZ 104.175 615.109 null] ->> -endobj -223 0 obj -<< -/D [11 0 R /XYZ 104.175 642.603 null] ->> -endobj -224 0 obj -<< -/D [13 0 R /XYZ 217.609 652.878 null] ->> -endobj -225 0 obj -<< -/D [15 0 R /XYZ 230.171 444.926 null] ->> -endobj -226 0 obj -<< -/D [16 0 R /XYZ 189.908 709.381 null] ->> -endobj -227 0 obj -<< -/D [12 0 R /XYZ 104.175 736.654 null] ->> -endobj -228 0 obj -<< -/D [13 0 R /XYZ 104.175 512.603 null] ->> -endobj -229 0 obj -<< -/D [14 0 R /XYZ 104.175 416.743 null] ->> -endobj -230 0 obj -<< -/D [15 0 R /XYZ 104.175 360.785 null] ->> -endobj -231 0 obj -<< -/D [8 0 R /XYZ 103.175 774.515 null] ->> -endobj -232 0 obj -<< -/D [17 0 R /XYZ 103.175 774.515 null] ->> -endobj -233 0 obj -<< -/D [18 0 R /XYZ 103.175 774.515 null] ->> -endobj -234 0 obj -<< -/D [9 0 R /XYZ 103.175 774.515 null] ->> -endobj -235 0 obj -<< -/D [10 0 R /XYZ 103.175 774.515 null] ->> -endobj -236 0 obj -<< -/D [11 0 R /XYZ 103.175 774.515 null] ->> -endobj -237 0 obj -<< -/D [12 0 R /XYZ 103.175 774.515 null] ->> -endobj -238 0 obj -<< -/D [13 0 R /XYZ 103.175 774.515 null] ->> -endobj -239 0 obj -<< -/D [14 0 R /XYZ 103.175 774.515 null] ->> -endobj -240 0 obj -<< -/D [15 0 R /XYZ 103.175 774.515 null] ->> -endobj -241 0 obj -<< -/D [16 0 R /XYZ 103.175 774.515 null] ->> -endobj -242 0 obj -<< -/D [12 0 R /XYZ 104.175 537.838 null] ->> -endobj -243 0 obj -<< -/D [13 0 R /XYZ 104.175 238.934 null] ->> -endobj -244 0 obj -<< -/D [15 0 R /XYZ 104.175 700.879 null] ->> -endobj -245 0 obj -<< -/D [16 0 R /XYZ 104.175 601.342 null] ->> -endobj -246 0 obj -<< -/D [16 0 R /XYZ 104.175 283.259 null] ->> -endobj -247 0 obj -<< -/D [16 0 R /XYZ 104.175 192.81 null] ->> -endobj -248 0 obj -<< -/D [8 0 R /XYZ 104.175 398.972 null] ->> -endobj -249 0 obj -<< -/D [9 0 R /XYZ 104.175 167.418 null] ->> -endobj -250 0 obj -<< -/D [11 0 R /XYZ 104.175 736.654 null] ->> -endobj -251 0 obj -<< -/D [11 0 R /XYZ 104.175 271.765 null] ->> -endobj -252 0 obj -<< -/D [12 0 R /XYZ 104.175 280.583 null] ->> -endobj -253 0 obj -<< -/D [14 0 R /XYZ 104.175 603.068 null] ->> -endobj -254 0 obj -<< -/D [10 0 R /XYZ 104.175 736.654 null] ->> -endobj -255 0 obj -<< -/D [10 0 R /XYZ 104.175 351.414 null] ->> -endobj -256 0 obj -<< -/D [11 0 R /XYZ 104.175 545.674 null] ->> -endobj -257 0 obj -<< -/D [11 0 R /XYZ 104.175 421.946 null] ->> -endobj -258 0 obj -<< -/D [10 0 R /XYZ 104.175 448.804 null] ->> -endobj -259 0 obj -<< -/D [10 0 R /XYZ 104.175 187.78 null] ->> -endobj -260 0 obj -<< -/D [12 0 R /XYZ 104.175 436.828 null] ->> -endobj -261 0 obj -<< -/D [14 0 R /XYZ 104.175 736.654 null] ->> -endobj -262 0 obj -<< -/D [16 0 R /XYZ 104.175 433.237 null] ->> -endobj -263 0 obj -<< -/S /GoTo -/D (section.3) ->> -endobj -264 0 obj -<< -/Title (Attacks Against Round-Reduced Feistel-MiMC) -/A 316 0 R -/Parent 5 0 R -/Prev 163 0 R -/Next 167 0 R ->> -endobj -265 0 obj -<< -/Title (Basic Approach) -/A 317 0 R -/Parent 163 0 R -/Next 266 0 R ->> -endobj -266 0 obj -<< -/Title (A More Advanced Trick) -/A 318 0 R -/Parent 163 0 R -/Prev 265 0 R ->> -endobj -267 0 obj -<< -/S /GoTo -/D (subsection.2.1) ->> -endobj -268 0 obj -<< -/S /GoTo -/D (subsection.2.2) ->> -endobj -269 0 obj -<< -/S /GoTo -/D (section.5) ->> -endobj -270 0 obj -<< -/Type /FontDescriptor -/FontName /GFBJLA+LMSans10-Bold -/Flags 4 -/FontBBox [-460 -297 1761 1134] -/Ascent 694 -/CapHeight 694 -/Descent -194 -/ItalicAngle 0 -/StemV 136 -/XHeight 458 -/CharSet (/A/B/C/D/E/F/G/H/I/K/M/N/O/P/R/S/T/U/a/b/bracketleft/bracketright/c/colon/d/e/f/five/four/g/h/hyphen/i/k/l/m/n/o/one/p/period/q/r/s/six/t/three/two/u/v/w/x/y/z/zero) -/FontFile 319 0 R ->> -endobj -271 0 obj -<< -/Type /Encoding -/Differences [21 /endash 27 /ff /fi 30 /ffi 33 /exclam 35 -/numbersign 39 /quoteright /parenleft /parenright 43 /plus /comma /hyphen /period -/slash /zero /one /two /three /four /five /six /seven /eight -/nine /colon /semicolon /less /equal 64 /at /A /B /C -/D /E /F /G /H /I /J /K /L /M -/N /O /P 82 /R /S /T /U 87 /W -/X /Y 91 /bracketleft 93 /bracketright /asciicircum /underscore 97 /a -/b /c /d /e /f /g /h /i /j /k -/l /m /n /o /p /q /r /s /t /u -/v /w /x /y /z 232 /egrave /eacute 235 /edieresis -246 /odieresis 252 /udieresis] ->> -endobj -272 0 obj -<< -/Length 843 -/Filter /FlateDecode ->> -stream -xmUMo0WxNWH -Z&T~3ڮzy87?nkNehܤ=77U\;?:׺v==onU;O^uu#½O -ۍ=٘a?kLy6F/7}̽][H<Sicݾk^90jYVH^v}0<rL ͯ_/CkBnyWTHkuqö{s\녚"p]ϞќKյ u/A )`JbD>`2$`TY'`(ZqBJŌ -)Ǩ%553<,(hlwB60aG+LgıcW c rn -q9Mܗ8% CMq.5ShrAI皎\Sȩ ]8 `Y7ь1Oyezl,d mYĸSSJf-1i:C&e c4R$D& -&+übLaj by+bYBg YJYYr֟bx(rGT̛`F+٭L ,C9?d+͊11ӊĊ׊T_~+Cg!o!_??/?㫄Y -?^B\jUP{xᇻL^U}9pQq0O}c}3tȢ}Ə!VOu˷ -endstream -endobj -273 0 obj -<< -/Type /FontDescriptor -/FontName /YSECYB+LMRoman12-Regular -/Flags 4 -/FontBBox [-422 -280 1394 1127] -/Ascent 689 -/CapHeight 689 -/Descent -194 -/ItalicAngle 0 -/StemV 65 -/XHeight 431 -/CharSet (/A/B/C/G/L/P/a/c/comma/e/eacute/edieresis/g/i/l/m/n/o/r/s/t/u/v) -/FontFile 320 0 R ->> -endobj -274 0 obj -<< -/Type /FontDescriptor -/FontName /DKDJEQ+LMRoman8-Regular -/Flags 4 -/FontBBox [-456 -292 1497 1125] -/Ascent 689 -/CapHeight 689 -/Descent -194 -/ItalicAngle 0 -/StemV 76 -/XHeight 431 -/CharSet (/one/parenleft/parenright/question/two/zero) -/FontFile 321 0 R ->> -endobj -275 0 obj -<< -/Type /Encoding -/Differences [40 /parenleft /parenright 43 /plus 48 /zero /one /two /three -/four /five /six /seven /eight /nine /colon 61 /equal 63 -/question 70 /F 80 /P 82 /R 88 /X /Y -91 /bracketleft 93 /bracketright 99 /c /d 103 /g 108 -/l /m 111 /o 114 /r] ->> -endobj -276 0 obj -<< -/Length 586 -/Filter /FlateDecode ->> -stream -xmTˎ0+$$0  -a#A%߯jD岻fc;Z̫MfG} q]/ޭmޯo⣩0Z^x]fkn{E+{*ʧypg6;5PVpH8$hmڢ*߄zR:")󨺠3qXysO'H)-"}[˺s 3 4{pYdrK+ a }ѫW{ Fvm7344AGc ڤ_86 -endstream -endobj -277 0 obj -<< -/Type /FontDescriptor -/FontName /TYQWSU+LMMathItalic8-Regular -/Flags 4 -/FontBBox [-24 -250 1110 750] -/Ascent 694 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 72 -/XHeight 431 -/CharSet (/A/B/C/M/P/Q/S/X/Y/Z/c/comma/g) -/FontFile 322 0 R ->> -endobj -278 0 obj -<< -/Type /Encoding -/Differences [11 /alpha /beta /gamma 26 /rho 33 /omega 58 /period -/comma /less /slash 65 /A /B /C /D 70 /F -/G 77 /M /N /O /P /Q /R /S 86 -/V 88 /X /Y /Z 99 /c /d /e 103 -/g 105 /i /j 109 /m 112 /p 114 /r -116 /t /u 120 /x /y] ->> -endobj -279 0 obj -<< -/Length 770 -/Filter /FlateDecode ->> -stream -xmUn0E"y$U6ɢ5h)8",c\Ws/.7?3oz(yѧ2zvAwG݌=yzVmMמMW\=j_I*Cn_f -&1y+Sw$F5? S4!1!r3Ҵ>Za?ɻ=ñK}:j=w(]UU#5dkuѥy e*x12+Sx,099)5tJN'{fS 2R̼  KV iXBRs>^ .KCc2c4&Wo"q8^zl p5u%=cK(q/?xQcc/s/G|-mƯP/S8+8 4fRSYZ"?.01шŕ[KPKS60e;U}Z8~Sg; _gvi;Kc g̭oZ ' L^ -^$K{)p/EX{)^ -(½ߎ> -endobj -281 0 obj -<< -/Type /FontDescriptor -/FontName /INLLGP+LMRoman9-Regular -/Flags 4 -/FontBBox [-443 -292 1454 1128] -/Ascent 689 -/CapHeight 689 -/Descent -194 -/ItalicAngle 0 -/StemV 90 -/XHeight 431 -/CharSet (/A/E/F/I/L/N/O/S/T/U/a/b/c/comma/d/e/f/ff/fi/g/h/hyphen/i/k/l/m/n/o/p/parenleft/parenright/period/q/r/s/t/u/v/w/y/z) -/FontFile 324 0 R ->> -endobj -282 0 obj -<< -/Type /FontDescriptor -/FontName /ESBLLB+LMSans9-Regular -/Flags 4 -/FontBBox [-433 -313 1466 1155] -/Ascent 694 -/CapHeight 694 -/Descent -194 -/ItalicAngle 0 -/StemV 96 -/XHeight 444 -/CharSet (/C/F/M/P/R/c/d/e/hyphen/i/l/m/n/o/r/s/t/u) -/FontFile 325 0 R ->> -endobj -283 0 obj -<< -/Type /FontDescriptor -/FontName /KFFWBM+LMMono9-Regular -/Flags 4 -/FontBBox [-451 -318 734 1016] -/Ascent 600 -/CapHeight 600 -/Descent -222 -/ItalicAngle 0 -/StemV 74 -/XHeight 431 -/CharSet (/M/S/a/e/g/h/m/t) -/FontFile 326 0 R ->> -endobj -284 0 obj -<< -/Type /FontDescriptor -/FontName /CWCKIB+LMRoman10-Regular -/Flags 4 -/FontBBox [-430 -290 1417 1127] -/Ascent 689 -/CapHeight 689 -/Descent -194 -/ItalicAngle 0 -/StemV 69 -/XHeight 431 -/CharSet (/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/R/S/T/U/W/X/a/b/bracketleft/bracketright/c/colon/comma/d/e/eacute/edieresis/egrave/eight/endash/equal/f/ff/ffi/fi/five/four/g/h/hyphen/i/j/k/l/m/n/nine/o/odieresis/one/p/parenleft/parenright/period/plus/q/quoteright/r/s/semicolon/seven/six/slash/t/three/two/u/udieresis/v/w/x/y/z/zero) -/FontFile 327 0 R ->> -endobj -285 0 obj -<< -/Type /FontDescriptor -/FontName /XCHFVP+LMMono10-Regular -/Flags 4 -/FontBBox [-451 -316 731 1016] -/Ascent 599 -/CapHeight 599 -/Descent -222 -/ItalicAngle 0 -/StemV 69 -/XHeight 431 -/CharSet (/A/B/D/E/F/H/L/M/N/O/P/R/S/T/a/asciicircum/at/b/c/colon/comma/d/e/eight/equal/exclam/f/five/four/g/h/hyphen/i/k/l/less/m/n/numbersign/o/one/p/parenleft/parenright/period/plus/quoteright/r/s/slash/t/three/two/u/underscore/v/w/x/y/z/zero) -/FontFile 328 0 R ->> -endobj -286 0 obj -<< -/Type /FontDescriptor -/FontName /XPKLFT+LMRoman7-Bold -/Flags 4 -/FontBBox [-544 -304 1774 1146] -/Ascent 703 -/CapHeight 677 -/Descent -194 -/ItalicAngle 0 -/StemV 139 -/XHeight 444 -/CharSet (/plus) -/FontFile 329 0 R ->> -endobj -287 0 obj -<< -/Type /FontDescriptor -/FontName /DTWNCY+LMSans10-Regular -/Flags 4 -/FontBBox [-420 -309 1431 1154] -/Ascent 694 -/CapHeight 694 -/Descent -194 -/ItalicAngle 0 -/StemV 78 -/XHeight 444 -/CharSet (/C/F/M/N/P/R/c/d/e/hyphen/i/l/m/n/o/r/s/t/u) -/FontFile 330 0 R ->> -endobj -288 0 obj -<< -/Type /FontDescriptor -/FontName /TRLRDJ+LMMathItalic10-Regular -/Flags 4 -/FontBBox [-32 -250 1048 750] -/Ascent 694 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 60 -/XHeight 431 -/CharSet (/A/B/C/D/F/G/M/N/O/P/Q/R/S/V/X/Y/Z/alpha/beta/c/comma/d/e/g/gamma/i/j/less/m/omega/p/period/r/rho/t/u/x/y) -/FontFile 331 0 R ->> -endobj -289 0 obj -<< -/Type /FontDescriptor -/FontName /OAHGDH+LMMathSymbols10-Regular -/Flags 4 -/FontBBox [-29 -960 1116 775] -/Ascent 750 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 40 -/XHeight 431 -/CharSet (/O/Z/approxequal/arrowright/asteriskmath/braceleft/braceright/element/greaterequal/lessequal/mapsto/minus/multiply/openbullet/universal) -/FontFile 332 0 R ->> -endobj -290 0 obj -<< -/Type /Encoding -/Differences [0 /minus 2 /multiply /asteriskmath 14 /openbullet 20 /lessequal /greaterequal -25 /approxequal 33 /arrowright 50 /element 55 /mapsto /universal 79 -/O 90 /Z 102 /braceleft /braceright] ->> -endobj -291 0 obj -<< -/Length 1001 -/Filter /FlateDecode ->> -stream -xmVMo8WhCj~H\HrhSbd IJ!ۇռâ؃޼!9_?7?UepPgww͡pcӷx6׏;[Rd񟇧}z eq<÷LUJM롯{Ni~l1>_\}~8ȳ&qq;RUl, g^Cs=~k*[4^͖OmTI:/nY㵞1Ls*J`#l neܢ8Wi+xA= pMn?SbZbh`-؁6+ҖtΘ 7 XB[M98h򯠛& -jwJ7ɿq/1n^i 1z1MN F_ HĒ?K|M,愆f[ -eR SxK¿ec QR+ey -h_8khG_=soSs9S[<9^r%Z:k`N<'{>[AkZ&# -9%F-܂ϩ=WC'}k_KRV³ᯌQV -$!6n/xzjgu -endstream -endobj -292 0 obj -<< -/Type /FontDescriptor -/FontName /EDGZDU+MSBM10 -/Flags 4 -/FontBBox [-55 -420 2343 920] -/Ascent 464 -/CapHeight 689 -/Descent 0 -/ItalicAngle 0 -/StemV 40 -/XHeight 463 -/CharSet (/F) -/FontFile 333 0 R ->> -endobj -293 0 obj -<< -/Type /FontDescriptor -/FontName /LJRDHD+LMMathItalic7-Regular -/Flags 4 -/FontBBox [-1 -250 1171 750] -/Ascent 694 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 72 -/XHeight 431 -/CharSet (/A/N/alpha/beta/c/comma/gamma/i/j/less/omega/p/period/r/slash/t/u) -/FontFile 334 0 R ->> -endobj -294 0 obj -<< -/Type /FontDescriptor -/FontName /XXFXYN+LMMathExtension10-Regular -/Flags 4 -/FontBBox [-24 -2960 1454 772] -/Ascent 40 -/CapHeight 0 -/Descent -600 -/ItalicAngle 0 -/StemV 69 -/XHeight 431 -/CharSet (/braceleftBigg/braceleftbt/braceleftmid/bracelefttp/bracketleftbt/bracketlefttp/bracketrightbt/bracketrighttp/parenleftBig/parenleftbig/parenleftbt/parenlefttp/parenrightBig/parenrightbig/parenrightbt/parenrighttp) -/FontFile 335 0 R ->> -endobj -295 0 obj -<< -/Type /Encoding -/Differences [0 /parenleftbig /parenrightbig 16 /parenleftBig /parenrightBig 40 /braceleftBigg 48 /parenlefttp -/parenrighttp /bracketlefttp /bracketrighttp /bracketleftbt /bracketrightbt 56 /bracelefttp 58 /braceleftbt 60 -/braceleftmid 64 /parenleftbt /parenrightbt] ->> -endobj -296 0 obj -<< -/Length 1026 -/Filter /FlateDecode ->> -stream -xmKo0 ޡ@wbKE=îv;pCL2bzn>|ܘnxv%p[)OM5ף/ߝ\qh%-p< ~۷k'}r6?F<.oƓOVn<k~I1=9;[ˡy6Rw2)]~C2Dww<_ws1vn<ďqǝ{r?x),9|?\LR`йiߺq߿.I㻦\}𥹢9/85dNrf=KʳXxΈ9&^zz_/e%^I%Юskfy*x7`?J#+ ruAι.Ț낼 \duA\r \WyUb^卼:oy#yuȫF^7꼑Wl8/a9/Qr^8⼐Wyޅlf`;%[mp$[MyX[R+IL6`Yː 9HKvvI6)+Kk ㇹ7+/Qe\G$@if<`F[fĩW诉70O*Ƴx"ÜE)=+b~sN~v?SȆG?r#W?r#7?p>Sfcʥ~dFbw4ψ}}kfGl-?r\q# ?zSf fWKfUM}k5sBoh:0Ν4}{CUNzVcC6&9&jQ,^ktfj)B5&^SkP{MkMC"^+C*^kP{BEքkm V:^LZ"R[=nj lp\u[#CWCi8,ߙ~4?s -endstream -endobj -297 0 obj -<< -/Type /FontDescriptor -/FontName /UJZBQP+LMRoman7-Regular -/Flags 4 -/FontBBox [-483 -292 1562 1124] -/Ascent 689 -/CapHeight 689 -/Descent -194 -/ItalicAngle 0 -/StemV 79 -/XHeight 431 -/CharSet (/eight/five/four/nine/one/parenleft/parenright/plus/seven/six/three/two/zero) -/FontFile 336 0 R ->> -endobj -298 0 obj -<< -/Type /FontDescriptor -/FontName /CIXFWA+LMMathSymbols7-Regular -/Flags 4 -/FontBBox [-15 -951 1252 782] -/Ascent 750 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 49 -/XHeight 431 -/CharSet (/minus) -/FontFile 337 0 R ->> -endobj -299 0 obj -<< -/Type /FontDescriptor -/FontName /PEZVRG+LMRoman10-Bold -/Flags 4 -/FontBBox [-486 -295 1607 1133] -/Ascent 686 -/CapHeight 686 -/Descent -194 -/ItalicAngle 0 -/StemV 114 -/XHeight 444 -/CharSet (/D/X/Y/e/fi/i/n/o/one/period/t) -/FontFile 338 0 R ->> -endobj -300 0 obj -<< -/Type /FontDescriptor -/FontName /MHVERS+LMRoman10-Italic -/Flags 4 -/FontBBox [-458 -290 1386 1125] -/Ascent 689 -/CapHeight 689 -/Descent -194 -/ItalicAngle -14 -/StemV 56 -/XHeight 431 -/CharSet (/A/C/E/I/L/N/O/P/R/S/T/U/X/Y/a/b/c/comma/d/e/f/g/h/i/l/m/n/nine/o/one/p/period/r/s/six/t/three/two/u/y/zero) -/FontFile 339 0 R ->> -endobj -301 0 obj -<< -/Type /FontDescriptor -/FontName /PSSDWM+LMRoman5-Regular -/Flags 4 -/FontBBox [-566 -303 1772 1126] -/Ascent 689 -/CapHeight 689 -/Descent -194 -/ItalicAngle 0 -/StemV 106 -/XHeight 431 -/CharSet (/one/two/zero) -/FontFile 340 0 R ->> -endobj -302 0 obj -<< -/Type /FontDescriptor -/FontName /EXIYFS+MSAM10 -/Flags 4 -/FontBBox [8 -463 1331 1003] -/Ascent 692 -/CapHeight 550 -/Descent 0 -/ItalicAngle 0 -/StemV 40 -/XHeight 431 -/CharSet (/squareplus) -/FontFile 341 0 R ->> -endobj -303 0 obj -<< -/Type /FontDescriptor -/FontName /CKFRNU+LMRoman8-Bold -/Flags 4 -/FontBBox [-519 -307 1704 1136] -/Ascent 705 -/CapHeight 676 -/Descent -194 -/ItalicAngle 0 -/StemV 122 -/XHeight 444 -/CharSet (/X/Y) -/FontFile 342 0 R ->> -endobj -304 0 obj -<< -/Type /FontDescriptor -/FontName /MCLQZE+LMMathItalic6-Regular -/Flags 4 -/FontBBox [0 -250 1241 750] -/Ascent 694 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 37 -/XHeight 431 -/CharSet (/i/slash) -/FontFile 343 0 R ->> -endobj -305 0 obj -<< -/Type /FontDescriptor -/FontName /ANTVSN+LMMathSymbols8-Regular -/Flags 4 -/FontBBox [-30 -955 1185 779] -/Ascent 750 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 46 -/XHeight 431 -/CharSet (/minus) -/FontFile 344 0 R ->> -endobj -306 0 obj -<< -/Type /FontDescriptor -/FontName /ONAHZY+LMMathSymbols6-Regular -/Flags 4 -/FontBBox [-4 -948 1329 786] -/Ascent 750 -/CapHeight 683 -/Descent -194 -/ItalicAngle -14 -/StemV 52 -/XHeight 431 -/CharSet (/minus) -/FontFile 345 0 R ->> -endobj -307 0 obj -<< -/Type /FontDescriptor -/FontName /PUUKUE+LMRomanCaps10-Regular -/Flags 4 -/FontBBox [-496 -290 1501 1100] -/Ascent 527 -/CapHeight 689 -/Descent 0 -/ItalicAngle 0 -/StemV 89 -/XHeight 431 -/CharSet (/P/d/e/i/n/o/s) -/FontFile 346 0 R ->> -endobj -308 0 obj -<< -/Registry (Adobe) -/Ordering (Identity) -/Supplement 0 ->> -endobj -309 0 obj -<< -/Type /FontDescriptor -/Ascent 806 -/Descent -194 -/StemV 109 -/CapHeight 806 -/AvgWidth 618 -/FontBBox [-476 -289 1577 1137] -/ItalicAngle 0 -/Flags 262150 -/Style 347 0 R -/FontName /XOUOBL+LMRoman12-Bold -/FontFile3 348 0 R -/CIDSet 349 0 R ->> -endobj -310 0 obj -<< -/Registry (Adobe) -/Ordering (Identity) -/Supplement 0 ->> -endobj -311 0 obj -<< -/Type /FontDescriptor -/Ascent 806 -/Descent -194 -/StemV 69 -/CapHeight 806 -/AvgWidth 549 -/FontBBox [-430 -290 1417 1127] -/ItalicAngle 0 -/Flags 6 -/Style 350 0 R -/FontName /EHICPI+LMRoman10-Regular -/FontFile3 351 0 R -/CIDSet 352 0 R ->> -endobj -312 0 obj -<< -/Registry (Adobe) -/Ordering (Identity) -/Supplement 0 ->> -endobj -313 0 obj -<< -/Type /FontDescriptor -/Ascent 806 -/Descent -194 -/StemV 114 -/CapHeight 806 -/AvgWidth 632 -/FontBBox [-486 -295 1607 1133] -/ItalicAngle 0 -/Flags 262150 -/Style 353 0 R -/FontName /SHWLBR+LMRoman10-Bold -/FontFile3 354 0 R -/CIDSet 355 0 R ->> -endobj -314 0 obj -<< -/Registry (Adobe) -/Ordering (Identity) -/Supplement 0 ->> -endobj -315 0 obj -<< -/Type /FontDescriptor -/Ascent 806 -/Descent -194 -/StemV 65 -/CapHeight 806 -/AvgWidth 537 -/FontBBox [-422 -280 1394 1127] -/ItalicAngle 0 -/Flags 6 -/Style 356 0 R -/FontName /VUPEFF+LMRoman12-Regular -/FontFile3 357 0 R -/CIDSet 358 0 R ->> -endobj -316 0 obj -<< -/S /GoTo -/D (section.4) ->> -endobj -317 0 obj -<< -/S /GoTo -/D (subsection.3.1) ->> -endobj -318 0 obj -<< -/S /GoTo -/D (subsection.3.2) ->> -endobj -319 0 obj -<< -/Length 28493 -/Length1 2450 -/Length2 27075 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴeT]5'5Bn]w 5ww#<3z~ͮ:U{W:^MA b2J]Xy -, [3+#33; -d/npXL]<)=i0(]<,j2م Ҽ<,,]~`c`w(#@lc072*0AoF+5`45j@-*@JEI]Y-KE$@ zj@7E7o$DԴ%X~`~67e?B͝@v-]\x-\]ANSrloN@[?q7{k%_ ~@h $ oAov{kZpXyeey m3/Ќ_1W' -v9EAozۻ:{՛.dl@-z{feMADQFRBUAm@oݱgtpg|"nfN ;mH%@vvo~OO. 'Oj{ͭՁI(#KLl@3zZ2&gN~Y~r:}́o/n@+o#.ۈ؛<2)6#Jv>@39"m9a%jkhl~"c;+[Z?+4R?b% 7>?zm.@s?v5q 4xKf -}[X4[sv\ -x[b|?NKOKo6[67j@7byCdW-,obfdx77OoNg+o M^uqV_T|?-/2ַҼ />ʿW\=.N ۛ(͕.mfE@AW(Û6,\,_zs]:ƿh h0 2 Ni)ȟ(a<*ԒZ2ц#A -*NYKd_EiڜX1~eAxOEBd8[Q= ]a޿f_6;O}*%>| y:~LWֲ ^8c ~}LqOaYw[BS 6"]|%g0`lHXiBdI4Q)<;0lD_\LJؒXeD(K,G_EdKQ\i>*W.X* VPxU|O  |#~(mmlGؠߴу HML{"_kuEq ,p.ܷuBfF9Z48%:CdpWX-*=S"Ih/]_-nN'ōUM3UcПӡ]v*1Zrе̘94.C ?[;!. Yt0窨f[LJZ|)zɪk#:+)8Bc=cFYfiF*DrC%L4ѣEh/9p[bNkSΎjWѝ/)] l'kD4DP*>µxLdu7Z4D%?5s!K= k _s=O`I*+ 7n7m5G;Z9=m;{& -qHE@aRbCr9㔾H]?˺ <m) XBY,_|$*Nlq%E+V߲䤳% p.JjQege:Lu33gAýaHA ➷?-^ΜHP?k, $:JD\ֻ//[0rr.,1s@lm ӧ?3>]{=Lr_MvOsF- Hb[bjO.Kw/ RK;1܍FjLJ|lTbeJ}ؑ=#hVPolsc#Mag>9"lO'EYvF)iG/6t ڈgWYt&B!ftEt:de:CwW$ˏDF{FԂ0Ƥ S};GQ=JM3<OmrDk5jl$tyS3 yh5gg!3|BW\j\}ݧTV)WBfeHuyf»b,(ru筿5x7# RIl7u}n,y1Xvf%СU5'I:!7BMYd+5GlPWL1 IM1nߓg"yFkyuB8k 33s+ibhsZxj>Ƴޅ"Jć|IuC.{emU#3Q6ylq#޺~I$$9Tq:p#*z%)k`I D%~wlDIGNNr>&v߰J"3Dy[=~bZ m4>.0 2F\ix-씱@8Tӷ^oo"JA$OHr&`/M}d -sYGV;R?Xƻi{vd,.M>k^*~$x1^/XfeOGFRC<`KoM3rD>MxE V(ʌ& ɽ//Hg.e~RMRIY`YI+GNr/BAxuU0 Chf$!i_3\m b~^0^J.aؚ$2vSqajٟ֯%&JY|8oq %$YIGsEC=[{߀)>yTmZ (MѲͩ %VPϠfD9 Bw2,#Y)xBY*]oӨ7YB/M`k(Sh Z!L/ cd;.je67^os=+wdi+d\TȾ28?h蠍'' MfDmG8wpr؄v{>AD9hIia3b,?I0u9TA?>nu|i1sidzl?ɣQ"pϋlbSscQs[E41<" ^QWiɃEf|a17[cZ6}Wqhz oJPB}z: Q#ƙ^P;U#M&7 ~$rNeևV:suk߈ S:4>'RTdDM@<@Yĕ~Phk1Q68 kv  3goѸQ| G/̰N|=rq}FDkbR0"(!.kTLf|mۡ 1JT|iT>Ӧ*be:&sIJNò2"}{G_hCu90,SH˹JH[^nM|bPUT15~Xdߢ8Pq %LEaV-VίFL - swJ!q-ϐs-r Ju_2gŏ`W<,ڝٴJE:TX:l͓|5(X:BpPWZ2s;4⠘rUԩ$eύy1]<( ݵԲm`-C)UYj*n)Tlȡ2=>&bb'5뻨v8י/E@(}~K g95K劉 $o&F'e 7W)G51F*bJ\Bu|rR<isl?xai1 v 7_A*|/^,+ ~!_d~eʌ3 ZY᰷@WpxϿerKDR5Zûx;wPmJCߨ:6vzhh^3關a?/t4Iii~nn簂mj WȕJgݚqkz -Eb98--:`KY_v QQFZИS"5$A=1ft#U)ִ'FeN&K{#hGbpWyG*hx<҈:4ݫVw ſxmMS -Ty + -ۛ҈c@'54}vGPt5aI*? $mZ _}UɟǦʖd6Yӑlm)ԤDgוqPUfMmrbS'O2yR<粱0_οB *k ~̼w45-nI\j֦(Z].iA:gſB4V i -G{sr3=&E9,&ڹMt?E0 xľ5tz\[1 ,0,󐗾S1NM}*M <O23ni#D3|kmhw3 -) -YjdJbUsFIhk t7ws?#kiձ؍ K:Pbf+J -ȎT$1/ѫC1?U4PSQ~Q;1b2Ԧ?K9Br5 9\i)⋘*/TI&L+Ӗlf<.7djy -0nSc&ry2p%R'q3/9uNjE]c2 MlV3ۥmꑳShwvQoe:lL03#LyGR7S>*>ScuMΘBc=M hL܏GCI8lwvW.4B/W3_G^;>8JaಒV>ږZ#ff];q|(Bqz 3uf&!IY,rg& 5$l?d!ø`-V-[-5D;dx*zpU%=/\* jKRr?ZTő_ L/*~vщb -j -}B'"o{YL۟7X'ދ(7́qց,;M/=uZ@39Nb{ -,ŒF ,9u=?k[x|gz8z+s#ްђ~Q˱ JjqV6YOPjfq9괜^'> R7%!Pp_Z`BC3Jhj]azӶmgH$$L4g*tG{֌$KH*Pk -;D Ŏ ]uڼ%A`a,?G+zpg'D!zע z':C`Vq -|lOyJơ%c{$1([(g]ZȆ)Oxk#j|k%MIv7ff*o!$k&! эss  |S$ozH9VP|JtN#r{5^q|OIn -LoGhIxM݌ԃ'Ӿ22ˋȷ4-yzCс`/?8x?y=Z -bԱ1//[MLv_gz ~H 'bCEa*YUtrL~!lxsg 0z=ȜC_ R}(d -+2 }HQzUw駗˩+^rtj5d~ -O$+;v”8ʂ?B|h{}2;uHλ:!LJF"NgM^w ;0o![rxxsZz<#8Qt -, mJsQbʑINs%5F'Z_-^vJme` 髾*[O]J&ܝqH춡5!!q?Z}ɑ'f練 UI⩃e@a2F{Lno97,w{.j!^ӥv<[[ }ֺʉirLZp V\0,IDž:ۼ꼚GRa7@e5Fwzl1l68*p k,ҁtNuS8?iwFZi -j!>w%Gh -HS$` -Ŭm.37^ x6=~ NB+=HnYhJl{QS4@SJ[;rhqk(:3tנ|sǦ9M&ͨq7z5ghgBl[*<)ΨZDiXAU -nvm }ˎYh #{Xzt既X6xnW -<uזdB-.dћA8W AQt5f!xwU`HHhGp`bx?P!].kInh;o,!HG3k~*T蚄I$(^21&^,>F9÷jC_P{W5*Օ b[-<@??*Zb{v^ĂJn)(Y=q(^8vP@Mfİ_^:m<|UEʋ(/lt &d6Bɽ!Ҵ Pw?i2mEGF^Rwc㬴!tuvn6|q*% Hi0&u/5R -ǵ(9l vb5$%-s8okPD7U鉠{N#H(o6gC1 DB~9[0Y液bԓ !iK1g;v>*4{>ϯ=tM_ZMNzKGO6_3vQ[S Ii2R\2u*c ٪hdRѪRb.{يJͨ 5))ȣ(MA߳ c4`R+C?/b|̐TnfHI[U._&ڵ G"[<@ž\ \6:=Afu,U}\Uz^xXSeE 2E $"1"<໱L'҇=YM2vEﱨ0SA|=`?=kW -#Y1",[SRw r֝3 r@nj|*.ȱO1o"]Z&VL&7 xlMUX:Kdڶ̗ X Ǔtr"S- -~n*l SS?/㲷&GM#X ^ONc.tO<GT3 BTU7]yŋ)2ù`<m BOR EŀZŏsu!pg/']j~ul!bmZyFFUS -7GϫO; Jc8*Bj:jLNb—zH?QxUٮBw/;14x&_t9Ypd%Ej,Lv ՊM{cŏoa>!{2Ǵ@ B_D'1Qv2bl2L[0|:{IZ?([Jx'G~uɻU{\86j%[y$ ˌtB"[AΛPQ3V `JQ`NN=M՜ynF5˫bpr(hhZ*)tbDJx8q5m;]M{ lʋBn ;?x?g)&[0`.ٙI -! 2Jtp6f R-.  - ];{E;)uWRavK y ^[g&ttȒ wRi謥 oCԧEjqғ2yC$=逖tak}!z2釄4d,*>_ -Vp!coc\k,\t•M?=[^=z a`k>&*6*4 >KEױz ۽!DG3dfCGM/weQUs"lM)#=*H d~$c%ju${{ 2W}Y罯Qr7wB}q%OnoKkF1^"=KBjix EUͽp+.!Ow>CQ=fVw˗rOh5㒔F4AR?C\Q\At^FpO|]ĝٖnnJ5/ӸNxI Tvkѻ!nQ a`_0lo SC̊#k茧į)kYtVMIY/Q|$S.bwJUL5y816>Jf(gYWz*#֝n%!wY< *L+ΒV 7`8gRr*"k[,J]-#RV?:qC0HnhZRn[DjVTI1իu -|Cײlƚ3O8gk" <]-Ldqp{X6Uf9[2깄q B+| ^\ravLn= -:} ). *]_LyNЄpG*+#h%\_sH F@FܬKbwu3jЁHw!,e橒|_Py?De%u)l)5a V WɆEb {nj.OrP> v4.v'B:-r;#T/oN"[Vҽ9&shl|3 _Ry2@FJg*53'%fYqYo)-Xm1՝+t7m9H0IZ< Whg|TGL?|Q*N;foq/7D^@4J -C1KL%|z7hw %NNhX?E\БR -[;vvAqHJsAfu}/舄$9'3Mgp1hGc3]H=1s ݜ蒱*=e#@w[ҁf壢V~Ge, -2+UHN^u5Y2W;tMG~1ZmUC2[#jk^$pvM2&/~k@tFu-W7E*rkUWD|R2 O蓒js,Q[abZ'RrKL4ZXDyL3% Qc%"tGZ=+f4_A]AnIbiɓɝV6nܪOr SMQ= wHOGڔQL=-F&pu ٔ|d#x'JX1> NJVإ5*ldqmF0˝W$1ܪdAyq_$jP rL -1jt'=ah9==&SX"^#f2~WXҥQj(k ,Ѷ*>fIluR&9-M?YƒIriĵ` - V^rjPVechL_p. eYhAsOGt?ۚ]Av:B+Fqki^%E_ͳ邁e8߄Wbciο! -|v 9<#3QV! ^=:`]J~J~OlC#FǗPSJq57, =xg;]R9oOlNo+A9u_ -,)"x:wmw 8ሔlN}e !9&"7M- "黜eH60'p]^E^O!Aph#vf,b0 ԇ+Zr/{}0#e;Fi}\c^)+-1yeRTfkY1J.wȰx׍d2'^ЉʂDn\!~=$z2n`&Ox>/}"+sw[@ W"VCr2m-+$蚈8Y!O =FKhY1}|Lɽ)OP}aom )_biH $ZS(oOӽۋK)ۜRln;f|lQBVQb"\5Ѷ| --Ͻusy5FGV;}GA]h<R H%@|D(7П;=*`\WXǴsbLESQ/9q+<uɄrD*NJN)OEMJ>NJh/qn)WpD%U? :r=F~7#}# āEKu k|~W| (Ch%3Q%8@Z Ƥgt2&69%3*ΓYi'lW+s{d1ic1Bf9#8 /|2l[ZVc M; 9;„vc({F{vaka6_Mһ |}{poY_j3GAT{+x@lCuϵgfaJu"[1U߽ vKI(i6p0RNBYZ%$%lq6Gޯ@*aIJܼ4XMA;hts:EIUtK6S^G8ҕB"O, `2= ?tbKhupCnp۞@1CFɳMM5IOkBXSʼn,0VA1K=;p32Z iO6 # /Rqv֠ja mޑh<`\ իMsQΌadzX7\{kHgth"ggMYMҪouHhS܉t l'M"Wr#VJB|Xmkr~](4֢ʼZj/Zī-D\^%Sm!DLe1Ȱr.М,wm;: e]{2[:r(Kl0d-}P X3\|(OMX2u讙8~cך9{;{^1ks~74Y1 +| fB ٤>E3Q8MORgO vXf.鑓V,/}jZqoz]+Ju<2W:66~`s+lP gD;`&FuD37n$j -r(_C$X즺{\t.?c>OOEaZXQvR]T}7{WIrʥޝ$EK$3?ĕPa}& jʰ3Zl(% (]E]e=A`<jP!we6lB9}'=Mj+X- u>of@~2IU!E0 N*LAMYPEO`9 -RE" 2c6kvGu(Tϰ4UiyV_6ƕWK0! -9$[ƠYE[k/D -9su-_V)LpGi7jxQhI]Iru&QȻ% -wzKUyM"/ =@Q_dfdWyH/ӵ4 [cRLj9\8ɏr _ %s¶qDKJL1y4Q3㩄hZRe28*{]Sr1+ϧZ̢l9^ P[1B'FZ6ԋ`ZnBu?Zplw"AP0\'{2Ss@u1i}zʢ\z D.j::Wߕ)ȿp5=s b2A*5~BYtg&XfM_53>;ΨsJLR=^RAj1/E6eҥWg[ZcqEUߔGpGS+PwjlTgR1+{#>FyJ kF@i$~Dg:+|HRMCU!bVO.fʕt_U$t EWR>ë$L+:Np UT#3VI׊t>.ڎgfXUb,)ԧ:ě Һ 8ZOYNgwI \x~Kji2# D[_.˷ȾE%REuv+Qv jk>Q1*&pBYIDdn^w ,S[17vd< Tx :FږYaby̯t--I 7=7t5rRYBEMYѰC!hS`sg8]Z<"?}T{/=Hx/cوLJTjjcM`&/W 03b]R'ᑞ - p©aqtFMnH\U!,¸y{SR)cKqUn߶^SETiD4?r -)_5գ$2rthDfwj NY(RÚY{r=Ńz97l͕? ]+`5}Ciۗyvj76؁7AZ:n -jHV10CPSjF3'ntS`BuV^!(O]bNM^S-Uk5 Xl$?$=oQbT!jr*ț' -)[hѦ"u1ìq4DgCH[ԛEss3lodٵi`=g|=[OwMdK\j,wK~< ژc#50Bk?ؙN">߆ >2b -x&6W,w9ǒ0%u6{5%&#r l8?ó[OY0(1{W›3Xu^CF%hueIU [M7-cǁZʝs[?2+OT7RL._n!f`a扣"xq 2_9w,؟uBs61Ț#[j\ ijΰX )=Wo$yrMx-vòqk)_^[@PZs1"e-wyÙV,u7/beY5"fsPQ͵3oYTՔ(8N' 7oMŰ0Ob[rȰ 쏌NMM~3)V9)#.NPg萼~ *J o;%V4zv\=h`vkXFt[~&东f9 ;Iq[_clIm貭M||֞htxN\Rލ-t962e%Vf 6*SB܈Kj?dpPv{߻*HEB)`M05h}NN(7VxuoiAP]+$^b_Dj.|Wm!Gf$qTȞ?oƠQ2x*9!H^>n{ͪg-Ƙ=TyMu+I>7`@;f}jX1$L\c61wU؊uMmVz5n\Ny5)oV] zzEP-wGh3UEsڱ`bg{?^?PףexQ,ӟg - -߾+#} #&V"( -G9^$yH&t;0؊4Q@C${ima6N.(PS7 - PGƅ 'G4.ah#caۄ';w`Kd13TF^7>DTн0,F;/@!q9uQ"bzQ1v^Z;P8y9'-IS(1sSJ.kuǪJaD99Gk0ai A`Jo~|ZAJ>ȡy?1zda`e<~>1[dY'9ZMpb2ydzh 9]2f6L`ƃ\H!nRX[$Y]8cra C*yMd -dPTH/ad7xyu}m9rE !`LQQT_I fy̩&+Um2p4ɍ11\<~6zكuW -e.eMgSEx%56 p䴧N+VYO? VOov#Ggǚ>DZƺp%6#gX =<#1}QJ/ӓO}m%O_fM믍(sf϶{.@N, K~'8mu#E␵Jót9B?-x$^O"ϲYOYj/0|[h5",^=Hqc -egcV/ɓddZ*MUQoۋh mc!H8 l'(_,Df*& ZU)sVDng vX iepf6~7s+cQtvLa6cΛKv~v3r'xU pPA2|_t[&5Rf ϡTtQ6ϖ@=pxa:mpُ x;ݖ}cƶ($z\MY3P:&Ţ}9詆/WlT%=H(v(U -t6f]YOsވ>\|||mK<^I?Q]NME~\ED{ "EbDm,I#6Gw}h&HųL) ½$O|p{oW>Fi_Y?0ƭ=yJZjRdKimL;ɦ a)B<[H<#<؛dkX>\~i+º@dslOzM<+?lfavm̡k\<.=ySnv-Uu-V%S]8 -Ui#r9;@,|~;bաX. yBQDQ:! 'ysB6$of׹H\+jtJ10p25jv,%'D[ʍ6"h ӸC:x&L扒ܒ)tNAe'ް<74O4hHB%3G4όd2eZq`E2E $ďMzgm[u -Sic-g.Ec\a;d=f<'Ri@LjE05eV 'c':'M6\[W+-)G(ҭ ACysfK0Y)FV}S\X Z9}[`4z:!l+l\<y"x<q3t>b7>bJ]KyYin{4m 3dž?0lNi.a#6@=JS?{߽ieϛ@4ѓNm&,HΆrZ|[lk)Aj&z4BQ$IBA/&|3r۝8{f x= I̼{eX/.JN9*S"mknjDh*|ݏ@xHˉvCGq5Q@K-]HSk5:u(|+Rw8]KXvD1887[Hli Ϭܼ4KQY=TW闶5&}N&jLv:X63h -W .aY|eJ<}G1_m$UpDeTj3&>"KVŭ: -vRѽ~ DIg?` ~|Y(6X:Dcn'.F[0o~ -8p,< YO D9*Q`lF$;E;ϹN/_E>QyƩnv߾ -U,'%c޼W -0CM:l672ڻUbw`-M)R$ԪJ6d+RwC8c/Duq5R"al(NDg %}o 2D|Qt|3uFG#HGgc˫f %y5y[owKKm#GA"hMx;Ҽ4A1fWit38anI_F^qDCЁOKtHa)MMS8&G$>HX?hKZMLCbO'sWR9KYnhq, `VjK,oxv  &!j}g`X%vNBpc, -B5qֹĂhH /VcYtܦ8V,|'10Jp>";6&O]8cRb:DljpF Nb^uf0AA#u?j%9:A9/l$M$8<2 ``Qi74l5&ӓ*3{I..!Ts1|ji!8uSWS\D>{0OHrH!>U8y r|Qb&MlGa9ͻ󴠠eu NrdkŸp-Y]kBFkzig2^4ϥ/w'F۶n!k&j͉ W=2Tԅ:*)7u{S)"-z!)f,ͨL (}VWC,A#m d!ŗA%% W}:fCbʶ k4]b>o56`g^uv\bF^5SH$ً>H[uSǹ|wg.uLtU<Ҙ6D}-juô#m~ َ}yYt_GGJ U |1 /j|#t=-tC O![KUۿ!{Q -_\"*,Ծؿ]+e%_lDG֖/;Nw,^:T<36X9s -~(\sfN[VKTׯօUd,+bsAC'J{/}Np޶[SrqN#'OYJWkzz4h%N٪SRBȒ-aFnUw5tZNAr[ؒo X>w8$?1*wb0{ZsL@TɌp҂9.$k~MP gGT'Op9H5sY -䭸*RQt1Y0d]nh6Ɛ5SC~~bm @;V &^Re3Z*)j-)>{0&ZeIK#!2/Knf~@i/A8Bo9Zпy/O^[F_0N={=ʒJmAO<%DZ&UнY9&oNeQB_T*-4/"*-~22om!5=,}S,ZgdE:)KA@(z(bX=)ҹX!NO_P! kU~+AKcT:@JYAϐ;Ry%jn:aygzvIє a!,ݖѪnIVW!L(91Nt/kRPuӟ`I1 尠9~I0u_vo<0> pŅN ĘxWOr PԳ=NY)2.+b=gﬕ2y-ۆl860e5D-g1k3,QMUo]FhN -7!~l+~.7WX\ᅎoǷ,KƕbJ<ǫ?V5͸şXSڢ8pi`*Yi&h0$qΆ\MVϧS I![,K4m -K˗Vh1;_\1V]1w]{ͻIA<{ ]%@DXU@V*k ׻ͤ >m|ֹp_[1ӦxlVC&@ۆ ٦?S蔜6V ׷/QhPB[5<( HU| dD" eox2F W{z3+;Dt7t@ -BrV+_u:`XΏ73,kQq6娷tK} !25IT2o8<)ӫ/ᲡbJZi .S]hzn棭ԁPۯIq@CE SOِqTkS3~K48{nֱmÐ~rH)_{̕)s71 -#[Ϭ8`:o#}No2 OtZvn|f69 z*_.c/" <tlL Z ,S瀚f?L+ IlD6m2ӥiC+ ̽,{09x09eׅ~`Fy]$AįnpXл9N ߙ5nI0$az`oA¢ʷ))*f'.lTNua$c\ƔAA~PRj^:R E6 $|8͜sD؛#.l%4:NTmR7?E,좭 K9B,c,K]ŇCdͻ-Gb3\~`B0?10N ;zG_[kbc  O4_JΝh -endstream -endobj -320 0 obj -<< -/Length 23211 -/Length1 1980 -/Length2 21959 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴeTڲ5ڸ;Cn! #{{>~AwҹjUhȈUL퍁v.tLY9e{[#;&f:eL hbio'jpXL\>|?,`@;Ӈ` z:FE{g:c#5H"bdin' ݟHF&֖#;S4=@Ch -lfU&@MELY BEXIDU@uZgUsZO?rbBZbL `-/njdoW7; 9_T-,NրW' ¸ڙ~w?498(Ӈ_> -'g ?X9+( 5sٙ|: }M)&:9!*_'ӵ5r3suGm&vΖ.G,m;3KdrBRb*tgG'gQ;zp2XM*fg*bokOD-?b035S{SW5;KGW`-3@GĂO¿变3#g pqrzS`jiWt);3{&&kT>` -4ewh &r)OM?MD-,-=.&oG ٙ?/ڟݏcg}8Kі&v@gg+_*G!G0hh ߶N0rr2ef667Gc=jˇ `fB BD#vA_ oa)/`P370 }c`gb dfaF&.}Z3S+@N?Cv$?h:~DG^û~#_?oTVk\}*.N@ Kӏ_u03rqa5Gx {xӱ23?cq111sߛ9.ٛX7UT}"?הNX͚-% -dJrڕiۼmLߛ* " ӫe˭TuSHie'OE:{bޑoӈu7 -?.09٠x tt? RaKC:|A=ַ@}EG|CՊ1E]3U1 ၣs>{xJDV0u"3Hq:RJ[#ꙴ`x1(FrM ~KW0T?htZErKmEayGL! -h״"BgU -;pP/l#v]$R"4PVr3>DRl ),ZI$(6+aBũ)nӻA]U:1f02V$`g.­8; -uo:G٩R3{`|J )FKbrqy+}\LKMU^ZZdou.(N[Rc8im!b{^E9l.P5lk8/^H/a~_Ͷg~}N%2`WE6:C^pbjԷfnJZ6ͼ1lZ#MwR[S'bH{miOS]-lU : - GA=gyО+[umzù)|#p371!(U0:˙տ҆fZ8JzB~qXNߡ o48bs#Ţ8Fn--_M= -2+=B Zf IYtC '+e$Luo,xHF!Wi{aR^QTW(Dm >WQczb00 뻙!3[9P -_&BXoDk;!cj\E'-u" %V6kX2rIV/޾VDfCe0)@g3Y]|Uij227N kl !Os3y~{vFO ic`!C`1j@9l"ΎĄv/BGk.Tj`[|;+5|tΔJpf7;m)cռ.Lӭ[ʔO]{0عA^?P{Vfǚ۔ؿ,PrRZt,D~G&lY`a0N2VqyE٣11D~˻J9GZ=M2̔I0eo^a8jXQ!6VWn\&!+~Jޢ X3$aJW!vD\:ߵ<1[CXD^8` jlO\ (mbF,EfkU;W+rOJpJ\@w!S˃s*Ypxe=Cٝ~R#8c3)1cR<+"ѩ[P#Rk$5yEiA98ZsYL\JϺ(ѹ2 헷8"Q5Jmٴ$;UsaDΜ0F)ޓJ~za#qΤa`n#lre0b^=dY4NMP@[zrjvra\T^U&al9~FYE)z85"pG -.^K6`..xk&38Qd-Q=s$}=T(l'*8qqGGoLOPBce!cxPmU5kf axj~J* e\ðD ~&mLTl¹>S"Jy< -GsPhuQ2ddjC7QUE(;8ROf/j^7?(H8=fԝgԋޯc-[a yiPz௣uLx 'U |/ N 2 5<ޚ'ujRA5KN:(_ছCժ.U^E!H*S4:<$e+p"/n n/ fIQ4nH͜mۍ֔E5DtԙڗAЌOTπ}QGEf-ך]$N!bSz>+~" D/t4Q=|NwOB'@4j`EA;ǽXk7 wz3Kܘb5Cc@ >ċЩ \ip&yĽ/}Xt  - >ՄƱr+5h6߇~ - -!Uάά,^xdfL%;*;!ZC[OEc W4WU7LKnĨ'iPo1 BQMRo5X5D'vú?ޟnE{v+ղÕ4w{- ie -<[ut:IDzE6,9PH=iSA3v+#:n}؋\'tسK'GЈEyi(r}/׹=&F$3Bm^«&j/jp/Uc>xrGOng"#ͳ8H}rF9O{d5h7`XVI!\Kw@fq,d8YiCj'jޛkE=4+iï5XHB6<\RZ[qnTy! l1e⦟3QE5 xbyXJk[ЯG=f=2jr6'䗹uE%}R> E.PU`tL}= M- sISkSIryL!(~,2x8ѻv8Ӹryg - E\8 A6!j⼱,P8$V|?Cp˩i֚PC}_AMVP,zȺާ/ qnaiSHE'G5"_Q—Hcȿ<(8OC=lvn*-kb7s) Lr]],z"2a~+2y )'j}5P ӔgdHv RTY5s%R O4KѼxh2l<`n@:ňeٔ`({R#ʎ.`\Q YϚɟW QKJg,b|LgپԷ\SVwT*VQc@ZZҜţ㖤 tbp\K0X0kI@)1Rh@W`R='&lp"O}GT{m;K.!;Dv2hֆ"\(?S!=rռe{h͞tJ8]´^(}9X.asv*V iʑءn]51Ÿ#z܍4&|{<>hy`MQ^4۾Y᪏+Ĺ\-;,`>c!kAԲ~iRO/,B\W,d}+(X|~|e-t>ٟ`pWWS)+0BMu{[5cl̋Eїe1e,:qUߡV>s!y_D'T 2Xل; -<`xLH<_}X#z ޙd9<u$l< VO݅o1t$:KX 634C;pVO< -mVpUBuʆż*~瞅حU$5gtAJkWL\$(ˑՈm; -2Pv]hh8~iZYsH9{+ jS"Y}Ъ a轃0\)GMJ셍hP|OOFN%$5ӭsF#Z{>ԲʺR5D&(Mh'\n P^l++ڑq/j2TfW/Xc޾ [5qLo%F/*P!(KBf\Sc/FEvoxհ0qؿ޻[`=^u\ -,Y4m6|SΪ$ZXO$ -ctTt4UnB-3_NNdDDۼU&TsQSB')x# -0|YeFK-&ˠո#٣W (h'GgSN")iA %:MvL Sl_vK><{:r594/ڢX4vQO?-t+#PPC([jy+5ԈWDn&Nœ]ZCz:h+@?;4[?ѣΩOlĸAjΙxi\`U4Cߖ;IAmO(HuemVe;Sc[GP^⢋У xM蠟!p3T}I7i}7exeo" -נG+%'4dh]GhwKOs4M`tP*D'7W୺bn,٫. "NW'WABC œs$RZZ-dQ͙ V;kYAva*2vWĭKbzoX8M좱FUT*|?CX܁D<.ZyۻƧ:5<ߖ٤ra>, AĐY$y909ʻ-69dU&Q$] ͕8{LPC6˼>0͍=ȁlt-ZQF3Ҋҫ|19Eu Ǟcy4l\22U'*7h3w̍#P$"ޖGh"Z;Z{;Zu`t1ۣ[F)9P1xēĩk4 kNb -qHD(S='oY; HWDUա}=P&>j|%0a8جӇuCџcY*%d]y@zΫ,w%oNThʃ=S[ WRx8肵dI܃c載 9֏Bc<\lm`Lw[Y{9F]E zAQ{6y%b&n]t'ehՐ`}Ih[i" Uq-L7zӗ^}R{c# zIiW`Q jݞ}(B[O_W_.c6=hKx2>/_0&'P")Z6s~'AE%7c1$4Bl6߯x!?٨Jc RtmO"ԑsop=ᙔ͢z% Mڶ;R 1zDњi1~m=?M˷i_$$$&tP05U&XJ%6L 2 sUQ#-st{ߦFn$MT~=|O06 ABGb2$^?{wƅ&ͪP4i!oZ1>u\p7I Od8wvpR%?;}m-jA-PvTu -8 1dfkob$C!H(;񷀋 Lm;^6䛥y{i;RyT"Z%n!-,^Ņ'MlL'ό?3N̋!ѪCRy^ >r -{N7pNzSc %\̡?눓-.4o"Hx,Ts7o~d~%n@4OkO -*d8 -ƏN+r1:%L=g@+2o^LRSވR]iFD"xb$8փ{DeJ<̰LA0B`hXCî`FBSܸa8asL-gG׈h.3'm̎$zCB[5r2ޏaf2m]pwj27iF 4@|0uOC0bKA*ۛ6mtduEko%x&3!WL!KiD1?{DÅL}kF:e.)9/t6%jN(c^_yR$)ucW|iRv2-0L_C:/_IAˈIZ~_אEa*C|ޓK/q?Sk5vkv`<&ۇc]ꝛR} n{,|@hCf.*1Y5YC;ổezL@0`'Us= C(sFL<{ †y(O/ %WDJybH εdSIlΚo/pj7OߙO3AUx IC( xx~k\`k9a᝚da7=RyD3s:V)]Pdź svulnYhT(RMt\,^ -}0mg* :aUfء<yBS2/:#U(g)LzxVS@&u8q登Tnt._n­96:?> .h6wDbW:yR6`!@|c1(up u׶{dK}W"˺H胛wJ˒85`kJ#mTRcFL\R~i[{Z; eCDٖ;1LK ._kEα&g^l,`T53r :cҁ'q[I G'r}QjS$OWœ.p 5Rnt+]dni}ٺ\v3(1"Ch/Qcwq }9NdT -}pl@ss%OK/+tbc5^Vmݱeaq[+D20VHf4_!v-yV*H%mY]Xq{\C; !ğqh]|, "rOQI.;7v@js>|Qy![3::(Փm6F-wʀ2}P]O'A5i08Oz*ӟU_)X ЉJͣWB}1c|YWn Ď`Ӿz֪H@Me()6ķq +vSODxp/u1E1~JsOdWQ𧞅Zλ1xi{ps]g~ Do% 4΅9O%⣫WãL!)vӍ͞3 g9 -~I,𓭲I[B -r " ЃG'.X8CI^Ƅ@ǻ=% j &hq 2SBiJ͈,˽,Iq+p_0>J1C* 륑8%V7[fc-We$=C0}P꥛b 6&+^n˞B(G^|pM0SsF}9l|!v"c-A ú>`UaC 1#1sZ\ܬgAi g1Uu/":}NsnD^F6y[("wlMČc-7ZϞ%k_WxHd:2X/ I#hb+=퐑41P຤~kcM+J6Vtv-]?.ÙoYǹ03N=, -VWXfדdC8HvZc{|y2۷_rQ~"ʠ7YG$zV^Hj`ɱwRܩ1|`SF~Fe4c#T[1\YFF_R&go}dD*F.&g)YWҲOABMj'^Q@c&ɰ<ֿΒ66}c~O\((`Ѷ [qfD/{/F. ע_tiڶgyX27ǰVgE -3v`}`-Q yCU_a_3NjwIpܣԕcaXސf׫_0-q|o iwoP=4LwdRX"PSl[Cf7]1e8VNO |syx|L>l12DPW |/5; 4QH k;XT r"UD B!Z2 -+gu(DC:1>;޺@~U`k;=> -2TwLPP]&Eպ g=׆/$`Q^[Jڱ;-Ow#FI Pq=C o?PDE5-La?,hôJ0 zc0I#̅pB#>Q6 BW̷$U8Yn6JV)AՙMh<7[ JN۴B=RNbX;Sǔ9h`<^U\7V!;噘W3Y^f[6 3,9w,-:ΰx=ԟМ7>~Bng'Fh76%`Py-0( ~}UZwh@:?,eޣ6Q`B'U>_#YȈLW9y:+\e|*%`7a7ts^ٽ= 8[Mۓ]T>uq)!NVS˗I5ﱙX\Ǎe12|%[)OU[hq\Fqȵt|O.]L~FM`HEB;7p{a=|..+=` *^W0la9@,K=ii 1JUH"A^OhLP–Lqﻫy6 -:OaۢmgOa8B' .|5#_>7#ܸ 9+Y҈T4 yڐVW8M1*xDt>}r%7୚JH͐syx`פ6odFBs]d)Dx`2Y߄]^{C0(qUcļX=ŎKr6EJN͏*o+,Hw_;(.b](p[Dhpڽ]t$/z- -@u_It3IT#9?;`:!g_VDQ|C|*΃@ Aǜx[_ȁ8/quZ -@v~{uz,R61lxMn\$uYRY=Kl+#L_ TĔȒ0_ -fXhJdUsӤB -y9u`0oAa.Zەuk 9FF:ZԸ::k(z>nԆMrɃ*Wgk’,{"FwpL+ܮŒg(YtwoeB>ji2I\xNхo淃D_1b7[7r+3~y{c@F]sXfysS̸23;MRq"j@n.)mO"R<>ZcnLI(l1ked6azsxoOăPJV_j&l!yz`1]Ή5G@VjvQ']RKepg߬!P;32+ڬLޥ=Jʊ1!@+Q3إxB;ȴ8hYˀ+8@$ *Q+*k&t%vcE.Qy09_6[)$zҾ5)jXy_򓋑 -!p s:*X0>&\9{RY.~M$5|Y+# l* ’WڜK RѹAR3â!q)ƋNEnu55/c簴D^Dܪe&,}9]-/mxq9ot2toGTϔ%nlydbB渤yfr$A>g Guei%}iN_Wn8D{J)fD8)CU!W]?w0H<쓃dOZvhqO'g/C#,#4m&zС _.?ewvG~3d]3Et3hp(|-Ⱦm Q1'bVGةNB!o?uR]@ EH2諮1Y2ב='[L;#;GӨi)N,)H' -(sP7baʮ$o2(*WђR;]0sʓ`ikT _kEesS7nAvWo}jyrXF`jRE[u -ӆdVVl3e['BV!95uRL`ͼ:+_x܆8$mDB8̞%M3a;npsUQ/=` -z[3Ed;ՀuyyNtXՊjT脅e3S%hnӗ`x>JSK jsf:` DR&bB;]PU"q ?`Y -wT$<[6I .?L/^ qso*HPU"+.=;Y0"Ё-v؞W;Hn5,$WɝEh]u3iȊWp,I9 >1QKANE/SYVo(΁ YMNwSce2 '͵\RMÛZn9] -7GI,ZyߧP/"PqB(n_:V0Mܟ(2|B|uoz#wSk'"VjDaOl8:ɬL(b_lS[ay+YeMEGD[Q3˘udzX)-W2d#X.;z"]NQ\[7А=)ہ" _",6ⴅ%ro*& =X^2q1T~ ;Q;:Vpʃw/Ll!'`,n, ٮf[NBS u^֣m96ygZg>neTtI$HG]0?K-3 -FHÆ^qgSs8id1$mLu֕jF[^wf#7Ў" -lVq"Zڎ?Og^Cit2j[Nli|XTy *^ <;z9(zšnlf5NG͹(5ĕ@(i;Ȑh&t<PO:t+͵_i˷$$ 3qS^<L"&W +n)Ԩb+z_AX- t`D -CBaS`DÛҥ`RA PhcA~(Qo׮BB@zrF42~(8^s.GR3apޯj*#b S1㱪 -hZaX[Q+JB<3(-ă?};?AD;|Uz%͹Z1@>p`{42Clj SuܜJFM0tSr)4 s0 -N)М$#H"XR>(s]N V'$}Z?*rTqCS)O5iavZS5˟[&BLB3ts#=GF pc|谊0}xü( :-8A{Ock zH $<}Ir<m -fFRk4O3=U@&X)U.F@ɃT k 4u{M[u HAV(BVo;ܣ|3wa;yi^93{-9{`z%/s^x@]EK_zNC.W 'm/3eb#sW(u1#_Qbh-Z`G1NZt^^#M#u pU7BXƟVA -2q=N2n}kSIH DF}혅O]FhlY臸fhP -\ )efqVOyk -+5߿>(f!7vi3h$CЄ&™D' F %Ʈ>ZSzMkPUa\fc{$#m௑4hmזrxFQ-RF9`bRr\.אV -d,DBqL&ZƓ[}}=kg(Oݵ)S]pj_kމ?XwV÷\!]Yݓy/CȋuI-}S&EdRu@xuhMMrqi9s0$Uf)Qd t嘑ݻE&.D6 |Y+X'1J,4Ք|ދZ-?C~+>Q?'3ΣMc7A?j{ѳsV_= +߷{5}q_0T0xAStW[*V}fdy/h\Y)3̽7 -줌Xz^K~sv7\(~o/l5/I+s`.h\HAb%ILZ`Q=Qf<'{g9i3/um@|0bQi7T\v9sTex$dҾB[> ʣ(WFc;2FERVނGdCX!o]1`s'XC0)lׯ`: @ |R3-<ED<,hݠ'Q\5@VS*]V :'8l lSθRHPۂ-oL*TQqRQϙGډnD~ǽ.:5exCNfhv*VAm:)2n1/{-{4OygKϢD5ӹ ¸o".RiIg &.%hUyHsX@Ip[ {G3y0+Ee sb,c$BXk+tOxu3q5tA oO&fRǟ鋬h|ώqL 1QkJM:D6}y?ezbl/r[875aeCNT(̝Hv -Av jt5VUd4._!ICTkQ$Y _ %W z姹$:'+zx L雟u0HQ GPw,zL 0rփꃚURb7q,iak14J jERT,HgrI(zzbUZhRPMHa+ؑwW[]sF{=-S#80xoos2Q^jkuIw|i~ra4$"gE޼; zfT^YBm*>GAycōSI +)#W5&+U;K3.u,ʫZj ֣*]̽1Z.ZQ68u䭯&T ԕø(;>m.c+?QT< EoFcKJ32x [7;t 5ٍD>Ӟzq6?%EmnL6]?@`)Wo׹o6;ɏDQ$Ԓu!+SE3J9>c6Kk uO'pJ[Y%Дwww,w $O/j Z +B[!Gv-`RfOVDuzu]5Sfia)|UdlUܺlhoH ^r7@at /TQlm7G 2-r]B([\ -S^*h|`O1Wmw))Gx_ly>LȪo}p,™)š%3 qͲtOzoF: |ۄӣ$P -u8 -:d{&>*2 ߇_'x~#INLK!԰n:5gvLic"hAg!w'A:#o䘙|JzǗ&C:ϺT -S,#"OG1~/zTŏ g -endstream -endobj -321 0 obj -<< -/Length 21950 -/Length1 1736 -/Length2 20814 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴuTݺ-]; ;^>瞳~##eG\Y#Td*L 3PPT9:2M]̬HTT.@S Q wVV>$*4ny`S /'  -dfn:Z8CAN^.6V98f- 35yL-r̊%;h9֦̀%@TTSH)k1'VwsrW/Ҍ Q% IP S޿#@IλpEI Q ]I6{܁.6GoPK?`? +bdO6C;`k=97H -/;A84NoN\+cmO t4u4w\&`oͿ\\Po)߭wf`g'fm]m\XvlEd$5ޅȤzgǑ o>Q ~/+7.RIG q{׮H铰y rbtmpqKG ˿[9h:8e%B -5r/~ 4wXH>@ BbXؘ߅>,Hduwߦ?J> G{/E ~?sLI:{xhJrq0'Bln/b˂Mߵ/he|? Ϳd`bK%intuppc s]*$A6Vv.nع>lz #pr,A.HOrf8=?(:9,tg rg{' t[T;_w?ku mc~/ES>3T35+ZL N;;o=Hˋ sϒ3T|̧UB:r0˙3D;@ր," @׍ U_?Ii1ke).Ttre'4Ǐ:{b٧0 *׾zϳ}v\@E4 ~{1]aR;.׃uPed)8 `=b*;LIh-5M;o$Q|e1Z uu"<=j7lHGl+Qvk6'QAzKToEs|aL̖IeV= BzYm8bN4I]]JRE%^F'B}4}XcY|ݒSzaԥv 8Wnq@*{5RZbz= WNAՅ* 846ZNW\ RЅ6~O$>+Pua=JWִ]tXǩeX̉ =fc>}]s,IO;oeFr(Gtlw!皒-olghQKɋ _#TWik<ݒߴf094FLxǧ2؛Q,l)hƟO(D(xD(h%9~IH"lW߰SEUF JZ- -IncLSbq#Vk({{H\o\I5Q/1w>^lfd6fy{٦amc@STр? -|}Ut 8\K#ڧ5v@ E; ۍ9L/8$3y_d@O]z6.u楦DqdkvYYa*kÔh~@)|t-Ncuڊ'w.^}cJ<~^Vvett AtASbg^4{v煱6"$]3>+ -ؑ~h};$[$&[NxȖ'LPvRobKOlYLa~S6+Ј%B4y{?*Tr [YW0AvψDхNPL4zVlW3]Wm#$-P0I[1 6*pzzɺƮ,nCCrvNI\}4FxP1vΖx* 5, Gn>MKJpM5n$VB]|s#v\b$w{U6eEJIT!H(hbo[>UP?2y@OC:57P ^B,K@QB8̹~'joL84(KĺcКGߩjc&wonϼ*zc[%нy[ӱcʗ饤[k:-4a! ?5r*U3ǗL(i(.Y呓0̯%N%j[,5oj,]Bi.Ҕ8e~F)>H0r-Њ%J߅MU253H-|M?S$>D3eg]LjV! |)͝&曶A !i} 4GVgOK2Yf= -zꃵ_R-lv7r![c7R$mY -(;s|sW?+%|ej\rJΘ ޾! -q8-O]-oo F;ڀ3&t9l#'i"T*=h.ϩ?EmCNcRVYa; V̱Y~u'H Y U۳ O)jIwɢi܏#peh۠MjQ%_?n_3 '4caG2HXAs)-1vYVQC1 Ls\{W8>5v̘땲r2}՚Ė ~:V˩AOWD #*:V"rFjps{I4SS\99 /*%zPiIZ9'af0Icv4._up!mILcl|cqZ(B07C鼏J3`:ʧAj4鋏3s uKo,V)A}_f5-O[`+AZ!eeM3,u-Mus0<TX"AU9-ͼ{>^ 0qK3?ŌK?}g2?甘0x~ -~^wpbrl0!mu=4氻<KEd·$d=?mvV r9E+Ԧ" M, LMr :3MiBӓ ^u1+ugؕ`^=]0͆5tJ8v5t2ӽŸ4_(7cS{BB ^hdߙϚ(Z )tf{..᫈}3,ugߥ+O~"@ -ֵpjɰ.؉.Xpf0C8y~ $Evӟ ㊃m~-fBK 2o(a5}֢OR9ۛs &$w` Ɇ툞Df܋ ܘ)lbR1/h.hRZ -wwq@EUICWa%5\RkKAG9:YMEw% o-kxCS(ߠH}t/c%u*n -(/qҒrp 5lmi 2,~\džX3D_LYu|D?:e£ lt1K+?^3rҩ$0Y;yYk^mlG %CvGZz4OhxD_K 9a(1DL@PNZ:) -CIZ"AsHSyd(lnm4`m&{ѩHVs[΃.iB<=k`ެȃH9%kؚU@qau[G-]JN:/ȍ=a[˜uQ /:c}Ae ./Z",)Q <)Of QKW#W \g`Gh&pǵZorDYnېQH=O/P؝ſڻ? 5hD2 {C}6az }}1C8$"gTZ<`}WI%!~ ۭvY}3Lt,rܽ#ssyE,5 9+șN}JGhL]Jv%'p2Q* 2iup:]] ^\J}NʇKՋ%g*ҟHp!T*ϲѝ*2 -Fy9ƚA/\ܷlAa+̩J$^lK5uqRy\$y>%,,*u}2`ډzA9cG ?-=]x5ܩtO DԷQp w%,)_ D5Ұc%0otQ˲WnBDEVx TigL0@a 𘣁̼hDOS4˯B|C#~j3s~yik飤e2v6͌A!L.ĽH$0i@8HvW@cS;4 !X74:]s#1pW4<XS&BhB! _$K-0^,CI$az|~]h欤Zf' ;$m~}@!UQQRL,g8)Q֍a@8>?̬Y?`$R`|b>A$-l/4:[3\;OtboݐMa[w#b1{x:<ߺX@_9P)IC"2w緂(rm}dbf+C>Ti}L̉/MY_9GRʣP-A^Ñ(yo~EgfG~^iǍJ4Ii5 -.f$1lꀵOR}T"6ك7/+K2YNݘ*'2R&n "Ѱ⋠9![Pb`&niHySOz9zcfn(S-ZxK4#R|*pp&hjGb0\,n}fac#ڋQ|dz\t5oUUI˂FT^tN[g 䂒ףy"v) 2 q;1xϭ𶞩ڴU# CuOÒ*vMjk[+dȢD*yI] İl;#*Vy >"NMӁoٗxO5L@T[De =Ī wRer NRݚ7柺։OoCqPE@+" {;ef_Ez"NRmna`4lAj -+BiF,ŕ9y![Cnܚd rcUYoxSogoHiM{^2诉t GR ܦ/S\YޚI -P4^J౹"Uz4Pܫuh2- _vO_Uji:A;p)ѡ1.s|[smXgKΦM|5@ݖpV>}`A*|8~4)Æ(iN[{ ͐*_#3(6}3v 7JH -ugDgLa@}`Azt[>Q+hX*XDkĿS72( "ț?.3c)nqWK8\9~:(y]l@EܺLwSIaNhjJH9=$u+Eu̴[/S ̾CŚ軾joćl3r4AڱC\>EގיǝPRwT -MsT2HT*4K"MMC]$e?E$@L{ۛoUx(i{O6{vkrWmp&+(|gܕ`)gCt:C)82p= !@"ENѤnnL{A6#z OK4ܽ ӵ?A}sKtΙ7ማil銍dR,dap|QSN<K(%1g+ ѭ*zt=#~Sł챌~?.)I5/P͟'\޾31#K{GVQhҞȲb\qwxIeԩCnnS6ˢϞPYOp-+?BGpN8 WţÒ,#TtXLj9WMlCH7jQ e>?m7+͡al}4.-h )Wu+;{r91A`Rkf#4l~\χ{oS: j)L>] U!(MOuy|f56t!=' <&fŰ=ΐ$uPTcͅhؑ_/8gthFKׇ}_FTvmCǼ\"$h<ŪER[y-yoɸt M hkYa䎼u/IeWd4j#)ܓ -<;Gvů|"] `ơMPG[oV\%W~G,tuך]qw ;+l7k m=w,SЅacdc]'DRHNN_:/fʕեCPU3D ~~G {Q)s iUN]+έH" '=nP:O+"),.detuұ<8?]-Y9V~PvKi D(>ܕ| dDJ<5I"T+_O*lB7 v%8 -y^%旼E3fݸ0"3LhgAACtS褴N)lKUH!x -A+^J:r^@. / 1*Nb}ƐS] 7L|yq @gR -{n^ޡ[pX^0'8*|0"&3Yraڴ]ώBzY/\>hud|s!a[hos,R7 y%tAu. -4^LLLk}EM _.Stl]n }gl(?b?Qΐ [Qh ohntf{fl`I -duA›W(2pY/ōA<[Wh{m@.G#LM(NXSy`$~)\^&*.5*A+#OiT3GJꞏ"($HN9|u0P7Dkc!䔁DSS͌a;q<^!! "~gFOD#t{ngO+Lz|\L`RnQ96Z<iX8ͅuΧ8haY ۥ33UWߜQGž -~:ԁ1+iըN@Nʪ,ݾv{ng賱^li'8c%n,"g}<&ޛ(N!{H^XyL^jp&nGߦۧ+T5:J2&ak9 _A+5\(nG.¯y JGMLj@Xy~bs]wH(ޢ{'v;$% [5}Lv+"%,B2UMSP -"ɪK -\7:6lo -d{ "*8Ld j" S{;|Thih߄iBaL=̃yjJ_}$d O8.}_K1kf -P`Y^-ΐed@T&5ʼC|2?ɭ$%5V[^dg =,N1, o9?l(ǓD -<$]rXw!%:vq0$/>oh iH.ddI*.%>s|R)R/@L6a{!jl - ''wz)C a:QD6)5tٚLvoD8Sqvл頜r}t3lӧ[&I-GqBJ==5ζ* wga1/n ݓ;֣9a>R/hVdx+>tVpj)tŞ׫⟣e qz0)@;}J= Ê\Ll:r|Cܖl][sw =\HV;%Kfq/qRSE>}F܆Oeq&8p N%Vt`b[+NBvMl9oV 62a@W3M`$*sHUshڝEO'~.FQPlf+5ȁL(ISa.G/A?#_t h-hG?b|4y1pOGٰh3/%H-J0ׇعWT &U@z [iY6żRXcR6CW>T`ƭÛI3W\j(MUX/uK,S$j[a,x83s@duTx>ؔ$+[n.g>]mI3^a^iĿپ+ NiUԬh} 굓RN`IF9113YIh.FhiM_mcϝLI&wel6sҕq[ܤYx, W9Sɠj!!~YI;h{8tYF5ޱ9R5v v46l]>V]D׉%!TDW/ -;G -Jj'4- ^ WA:䌇T\t"si^M^ l V$FOĞ%L41(ed(i~fVi0BFE\U,y - a?]0`-Fh*=~VdZWv{>LRg=0]6kعmki)jnrNj~3GP@Vؼ{F ݏ?|(N$GM -}E+{yk /l=6>l%{bCVn5#"gXNXR|H<*Kp %%Q -_s\yC%wD4Lb*9D"ۈ~mTAC(S׈ˊ 9R D߹fi۞ZP9~o s|4YNiQMd5]k ?!^rPpFa?l ߊ -m|62d9j#OU콀"kAe>4`ڜ3dܕ&-q'Y4˸/f"A>%MPJ,5%m\0!u? -*.Ӵ,kSu57Iŏj ,6%ch Xy7ZzgE*Ŕ+Чr}ʆE܉&NGhÏ W:]z3 QXx3?*6qBbAiP(hK~FXT{#ѭmu]: WXÆ-/VIꏑ´Sͥ,ދ{S^/T%N_D -srw]ç(}X>yH((ZR:\:Xm=ܧePwM WG8V |K1Q}תt]?pLJ䳨/ 㛥i{ѝ124;,OV]߭`G%VoA/-/7.Y?WX|zpkV6t1 Zn6W -6!O5u_Cl܄lw -]IFO-9Ei `ica\1-MFl 9'DHcqd6a@!V &@{1`! RW.4{fiKWՓ*|~B-&(,d-N̦8n30&Te~p썯=&n-#-2GLSD U6GZr̳KX^_,.vXEz.71_ߝۆ|M>z$R -s?D@C }8grX%vҶCʞ7-\)Iut#~gY3HIe"ki+5rl#k ?7n>k#ǻC8\| 1y2/}wv Dw~?%3^RfXn{Z/awd2cnIk|Щo.P>*`Z{#]p+r{ED<9J0Cm8_'ym1pu㔫QJN+k'`Wu|en7&}~\xqken{%r(U:gsi`6QXjf`8s䡄>r(` wbXxobJAL$tV1;,W>*qWpoPL-vY3kq6ZipqJZt{s*usIۨ\Owy^1f 1TБ,+#ոa7哦\begY+#S\}}v:g*lon풹8y `j"_.n`S WTK(\#kҵěs+ۗL@VFA@}X񬌓J :x+;2S`r2L(oک;Sދf硂RELPP_%;jgy9̈;+m0]os!->t\‘'z}%FlֳS`jcA/>׻Wp˸ YGiMs7iM܂ $kmYr#*u>@QK#_ZWP5tHU_Є%eoRl* vOH0{,fG'LɱUǘ}e){O;N7Vdn$͘o&\ Y`!nykfw$#%9 a::[%LVL -U}n_wO3Å=d2`&X.-baՊVd)Dk6E˻gUrF&Id'#ZHXlfP ijr¥ͦqzl29}/1f`t6qdE'!Ns^RЛJk>>|Cy -s`oiz}A oUkak8 I%;2 :7K0']jfS?clDžIKzwUk+BWm- 蘥gm}e֊U 'N($&3A@ig<8vE0s^ga"ۂGVi|kõOLbxķr, !X4]ufk( J̓)4"|ۭR.yj3Uu+R3W0{W}SU2l^#YϨKWڲ65f2,V3rxEq04/7 Xˋ?5{~h)k' Ҋ=.hwnqFxA:; :^mī}gNpq0eH%b1ETYıHB^ Seޜ,xȺaOē2+D\L5-KyCf?Vn`Eqaᢦ[iC9j4&! \T)=Λ9r}:to.%%p[&;~ `€)Mvsx$q<1_+=ea ?(K|A9^PK WjX6hC+h0Uج}a^d s=˂u%/s&Ѳl)Q~ q󂮽 -%^TQ88}HVx -f_yurzw|~a]H'p ԲʲP>D=0W-ҡ%P9b&']& -';! "=ZG,Y:_ /GC -(}} -aAJEGzc:S+v /T5JG'| #z/( B:|.eFAɆ}bFcɯ)iQq,5Kz&oo@EӋCymlu(H8}_n]H4әMDtQOђ5-UlkXRü|^?ɏ{Lo,>q3V+B 47GB^S6b=uPp\x4^u0jʎ#=.G+ Jx뭹 2sD(Ϗko7;^k_XD[Go9E7^}$Š -fDRЃn -Z"$q5.3nKhy?eo™L$ro/5Cd\('tȅ ZưnE2dQr:mh5Ρ͉{_ȐWQ!&ZK1i j8W0Fg+ |k=`@4Jh2'')%)hWc%6kNN:oʗzW>#[z0˘:/6rT!PtYdEauLc& -o\tؤeIO;s xx)e_])Jp?!bP14qQ&XZq+,_lln]1xk!F/i@TӱK ޕN텿F qҍk 5&efl(x7}i j1QhA#~`[|3M -{ܼ ]"wKI?ғ2!/^(7T{bƞ4rNa[I?02zDY`&' 2¹8qxΖmq+O׀S4m85&Snt-)s⍉qK3fDq#\CIV{1{PߕNYwOb3;^MCcB՛ :v]>M cx^ '0~T!Ro\@ggP U0=F>^!# -A/ -Ќd/) -idײ_P@} fzԁ^3V߭#c瞬î!叵[Sta D&5~ -jOc&&+BI&mO[PrNR]ܣ)v`lI[^A4ʅ$H ӸJpPoi~([)D0U[`Nj+5VŻ\_P FEיR.#۔n[̪ͬ?fhIJa̦𣞽DI,EWNTl%`@ -;׳L֘ƎebeW`$jS|taEb2.k|-ꦄ>t34{'uTB+';{h )m3 \F{ -^;cwޑFEZftXj?5 Z_ը_ \ _R9%? Z@#fIiTgGR>/Ӈފj<2S9(-}ذ/` 0J@.n#7pfԹ(A -?^0<v8P +iE!!|rt|c9=]Lyp?w>WZt8mkԃ{“ R'~᱙Ix# S܄o"5{i0څRa:J}ǁSS>6@)sJQH߻= $X"MYMA^$v|LKjnQ^k;V/΂'HI_atYzew+|(T~`~m_1DUe (IaM7ɣ(ӛOrnE<0!d b=ɰpXkRI{~iRX(Oҭ`D  -%=:dK54(ZS_dyŧ$^}YS~SN{?si8peW5}>ނ*T - șm x,9e-[\?X>S1, L-oZ-1%b~.AC;92ASjj6\B(@GV -,`1 ,YխZF#-xa9NK5OeHT%2,=U~ψV.r)NnC!ԁ ٬} 4Q~, -N .%mvf1Р6B-4n&;ΘUrRGo|^2i%.rpFܗ"@u"hERyA35̹ Ga |$<24Q@ι:03es(W:"2[<2b^g0I)`B'V*m8;}3{ =4a׮> -stream -xڵUwq;#JfEF)*;t{83y72PQ@A$}$Qr& $0$: DRpD*00]) -$yf!@$$4va0r Lu,PSԉ$֑WCZBb^Edg$`]0$zR8@HP#3 -`nib -h\672P I΢njf-hi8mnjicC35p/@LH{x$2nf;G =5ՁDto;R( -P'N@$,?# xI}b *GWqh@.T*IT;DPjdGG$y?WHpA$$ŝ ۨos׀ N"0E_OfCzĐwpF dBUpxpozޙ6UC-MS3 }D*;ŋWOUC_ө&NtqNMfޣOGB$yC DOt0{gqwpnƟ)To^hG^}a{f*!~DW'~8KFz㟈& `ph -UԵaޯCp Ir)a)uD+!t`)TilܿziHP` f M-lH$ Z8/cQ* DR!cixnbxV3$iľ ᩇ7:5245Jڏ$ HQŊ$̒TyH/y 赯 -!)՝8I{gP=/j,URHNF0jIPH^HZoDF22TD:$0v/d7$M)$3hPa#J* uM*lN} ~d|%d )8rLK#_)9n@4(}S!ʎ*]эOxFΌ|swln -MK,1VEbTc10 , {'ϪHf0>0YRolٌz˹,`}M.g07 5q˦MHd}^]F֖#l~ <ᦅ=[!:\!|7~ VɛXZ|TO&9M' q&^hbWj"LEQ1PEA#!3ӗYuOe4']+4f63ǒ -:-Nox}h{5;a l_uy /z1`ᔹUu^.2lǍuXDh޶3yBfif[gex`U:\o@Ǩ,~VsӆMOy2[LMՆD˛=??qY@􏨍FfLu[,Xj"mޟ-kX!vc%k5ca)5AqO&y{/Y@Y_VMFmV}Upꋼz^iΡE_KxNdo=row)yh>pVyGQ7~ɵ/mͷpGEcSc+oog,#^n~֣ȗg4%LjjIc#Ԫ%!c!.5wAƁl?P\KLZ>LcsK %n&o>CnU@U88NSauu@\uBϒCҕB/9d>B Bqቺg^jZ.q­與B'o-)x4uuz{,1ۺ ӳާk88(^a=# x=̉R4ss. ٻ̅6Lae*4T~0tyv%W'$P/d7iVSʵ[ۑ>W{XVY?Wi5WenFty!|tQ㓮`yinVHsϋφlvěrmH)4z)2y_rXʋgrYe!%)|NoUagb.v^f% } 5ݴdتD5)rp^}Ȉ`<]|[wov+.?=e!srqܭẉUAiwYC<6fڵv˝:r./dFnؚm=/LL K")2Tl G {]\^(MG-b zxEWKY1vzETY7QX1ϑ(w:>JJxjB-_E @+f<=ӵHϡpC z\H g:]Q1>z5хyy찁Olx@ig$HM5$<{(-An{#j9!oUZ,)KrƱ~3؈<>]HX*=qdܼ>_sP.qNX=ڧito}c$Hsj%a-iiܻkx/,<3__Z? Mf!lU6.WXoȲI9ԘZ"1DYz^T˭+P7=qU(V Ɣ2g{Zߊ86IQ>h͸ՠ<- XZnjq9T(Uf6 HŐ+17~tu]4UUl|R+b_z=>_R?rfIֹ)dz?Ug۰,[+[80z&i:eExHP#Ca:S[:w9}'糊(O^}7* D#zQ[l+|}@uY7"Goys}٩0=}.5d36I>`i)j\pC[~l׾uMڇ]Zǘn=FL1iFᏑƃsWk?.$Mp7nsxݺU]ouVLyf[:^ԖvqM}"VC8^=}|J;$I|];[DSø]IAg?v&HےjMIIk%=EB 5ƮՉ m99?tL]A|JUŠ8|^y?K霂 ᨡK9hljXؗ2P':.C9u6%vjZu햁 -ts z١euħ?igʧ.uVas5]|'`S@N}W -VtQ8#X}SOE +ag[G{_m /"1} kCW^ -E([1y{)7ʒrmԇc~pG\u|77E騂 ʆjZJp_:R#TO|t0G2h 6&IܚWMkwuXJϼxbqK -9еՆP"ݣ18r˫=!ۛuǂ}ޖ.kG_M-"':7Z܄k\|5jx憏 -XZtgj3ֽ`)kzM|NdWA8ȪsƟ$Dns79S/*_] X$VdYâCܸEBaJ+}dQBd =ýx̰u^vcזZ Nݬ͐bݕZ&6*V|;K4O>Mcu$KzbL?9r"6]]%/j&P %e'@j?Zv`Q;P&lS~T^MZw -B⓭w h޵;ےO _Kzufz7 -wy2`4>%Ʉqv|ɮ㟉q0fa߽sٸ -#C.OFyU@+b1<@\W|J P' -endstream -endobj -323 0 obj -<< -/Length 16640 -/Length1 1704 -/Length2 15563 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڵeXO5;$@pwwwwup ܃]^wݽw\3SU]O(9* Pޅ '`glAp5XEA@c+{1c hč@AF3'@bdP3Prpv71v0-! + Kso@ -`loag(8V*{ `PjUUT*J :::$@LXAMԠH}T(}p./&$w fl7q`/j ,]\y,\]@ Sr;lWOa\>b 7 ge -w pA#Qs 4))쌭]..;Ќ_QWo4_.2=[o_c1c{WgVlS{g+g[wgV` -jr³w=?p1qL"7u`|bVurqy2/];{oo\\b!ft0N%t/(#ke x;. W73'C͂LQ?`4G`Tpp?}o$\mmT^3 ˕Jdglo6+g + -/\CMRN8{]z.3}( `e¿(_F-YIM %M̬-,cC,oI= -# 0w!L6n=/1+3`g0X;W ?נQ'-?ζƪ. ǹ\]@VLb?nL$ o""zn.3'ĚG@SES`E UXZ2 P+?qZ(x"ƴ}Ֆ\=uc,cU+>Xz`E)L^v)lV{B;@}XwԫTRhy -gNNpGh^9P1ǞUh9O3е#ziiMFW1p!Y8=PXP9’xr1܈DhXOHT۾Cal( `%U0RtKoQ3b)QJnV]BvxKK9f\G]ƌ'3ɟ0Ml[cYxrUP^j\A3j_ŜaQ,cL]%U@b&^&+b41WF6QE][3 MF_}fySr,qtKoP p;.'1Eֳ .y kd$DVY U1mIZ|9J6*H_DIQps( 8FD&ֻ=CT^'zbC3_ik1-.f*-A=LaC4$Vd@Lj] +1taA &\lԳ$"fUZDSbOԡ>`…B&V 3V3uCQK;xA"$ -v&93"מktnLy@ )ŀe+&DɌ%zӛxr>T]泺,; Xm߶̷ϲw~z|udUUwZĢܖ j)!Ѧr^irŸvqfX^+-<9yٚ8(P^A^,βwkagdȟ {뤴jznl$waYv=Gu  -o\B^hΜܹg&)G$oT 95o2ro؞X9ep#R4rwn,xJXD!^+0dQ}.PUp! K)˗7er}L`|3x 9MG:*Y/*}cV+huܬ$Dd-W\!k9a!_XueCg!;5'h2*7tw|J2HmŰ2ZHi@ϣ$Bц;E[S.ˆq̝UЕTH$ɘ[wJkogWZ eUӏS\hVY ͼ\olM}j~"L ySz( z4>hP!}{4 !9WjdF]ؗl [lFe:m:\Ѻ'6ŧLKlHA&QF{u]) -@ssݶܗ3 UC Z%^qWj ,b-![ƥXӰ͒^Ьrv9uX==W:C:5Km1+O8`F؄urgn&愲wF`;Eh5ҘmWaQDf<4ӎ@x28(+h6մ i;93-Jr]8=H ΢Јy.d:/ (H%w$V\hfvp3TLh*Z6GH1G7]$nj.+H3(n. -,ר-(hSP\jm/E˨זml?B;e~YkD$aVs{`00wgК:-wmYY(VoRMqesml] I)9}p!;1+fݚh;I$6n -(#ȩfjvNZ } >uMZV뼨EYm =I\'ig0 [KO@~k ;_L:y=)XbG5{?A{Ԭ,Msg n$/9D͌A>۲upOunoECu*$ -V_ClsP?*ܳ E!Xw ~- *?!K)Y>d/< .ek^5 X5DO4#2"akbS$jTyd;KLJQaɬģ_&4EZ=џ-Np p0IJĞq3΍5-9z!'aQBm -ʊQKX lL#U8r cr3|G?n&aB_M~=hc}{LEsG1q"ҬLkMBDFqc"4lş2uV\9?_.Nfcѵ_}y;oH*SԱ6J'f8e'oyz~_ftvR8˔Tg?(TNKcZq{JQAEO<סHI>qRy}?q([Otcp;* ?=\.Iݏe E -OEe)Or8Zgm!ak+DS(yǪ?sAxX~.CΦ.c'MY4^/\*]Yx$GX⸪-)a^LZFZp.X^K)ΞwIm9`pXՒZ+ y\KaD¶늊J{xڥ&qp1&tQ7rnҢ!0{KNLۀYo=e`ZZK *ń]̚2TL+)21ިz5hXE t6lѹ)/TQr"q"C[ųr/rh WlGcdAz#2Z5Wː"WBu:)OHW$\&{Ux Cv#b-/TuYQO3Wn.0wp | -(n:O8軾` SP3QMtE)VtѨ#xKQw9r.J-nT ɸ-zBH-H{Һ;(Mػ6p=!QSQvYaVW 8;ZH\O̔8`O#'*?ig,]HWQp4*o"p(kF<;@FqlhvvڳuɓJH9T]fs^5ݦxIڐ=LUҠEwׇgXTt4,+y=ISO K`˦p -Z.r 0'6r訐$_L7&I-2TgbpZn}k湰x3e"y`IݵY%-_8VLOZO]k^Kg&!6tH9.fGS#,^cs#a"uA2"5 TzVGP -EGFƿg Oꓜ NɎD%FL[hI:Zhϕ c;(!=⸵cX'en_-FP!5YXT@aa˳!%5#(N kYdxG~jO =Eg-GݪݮcK,al32 SS,s8!#)1(A~ݔY1E]i}?gWm(Ns?Š+Fu&~ .vCXDZ}Os/A|"E+5X!8XC=$^2ԫXժ!F#Z.(]Bc_:lQKo -֥g -FɎ gc" -[,pɪ )f] ukzx&i;,P:yip#܃BГޓ /qeG3} -u^Px -I'txc ["[nU6k HgpI\]_"$$ݨW_wϦМMyHXtAk7"Oq=f'5)o1r2g⅑H;0 0T P]ry;GfHK 4~{jxubWo'ߕEd-':' -_t}*n&@|U-!ѿTs9` Jy#= w j -\Xthb-,Hmc9Y0"]mv<\J;o< [ҺW{д B ө U~#ZcY?zצFu4j7Etast S詮7+CKkN:Wdpf=~H!RS;<RBò-};RRgH[x))[pŨ* * 4![w.C\x:.ZOTb˵:3wcԪ&M)GwtaӼU#(MZɚsuFUJ% rM5\rSל7Eo<;XDٮW#RϡcMSqt kHta4Ef﯌0~q0~F'O tr !:pGyٮ󮑀$ݦF6EBKE̩IQ -GrXFNj $u<:Uͬ.ۇ!PE(Knфe#Gx+KPŇ]螱/n7SfI[gF÷mOVD9HJGp ;gU+]HE<4n|wD b2/XS2(]!gYdq6-OʒJRt[} ъ.ȽTRoVnAyCJ$soB OO:nH?t2V^ki ջ߻Eac /]sbDzlo ^lV,G1և)P -E&hBj%(0qd"<'>Xrծ"dP:o}ǨgWFp"9w.]zǘ6l]0odf>¦:iuMm‰tax"ꤜ&nxҁ̛|!_1( -$i6]`!iy{/ P)Np{=P2CG\[]g/|cTubDihYr7-n`S?1\sq>Դ/NFG$V֨ZYp&1^ Q5驀쭓Uhh/|p|`lx -QA|k 8r MU sWj SPANRYRw[3ˎt.Cl@G0ji\?VZv;{ȱ ~P5]{ `/wE|<~, f3H=)m@9Zկ+0;g؟1>f\젧+r˨5b0&zuE}y#[)Q//[\O Dwndy/tGlQqR$Q',H N3#+a Ips!*9Ҥz̲ͯUH=EW^$.qU- -@5a#ٌ. vd -&\/\J'ȂykhoA\~6E޺\*u5gQC 1ؐŰ]E$#[R%hi5 Kpu+c{ lHH p(6;-e?`MMx#*?9y_O\oPf.Zo@6O7XcP(WCKD'hV6U?r%⤖~2ː" L/D$V79cE^qvF\X2Pj$ݮ?TVKGR"KZ(%&e7f8_ -251 ]^⒀C#|7Q}D)|ynip@؞6ގz'm -{ر,k2w^5OiDܯx'4~r_ ]Adw#+N9Ze?+SDaH _zثv/d penw}ŎM|C!{?DlukB;̠p?gM#t 4Qn\wrvn'f%l8k,%/tՠ\_E;% BCi^lO -D4wOOn1'pkyo,Zxhrɏale5Z\m\n80]C>>Oǫ%Rezl]3Te0h8vQ j:Wg*2ڙx\nl4_q_ߏWp=Xm\j2{"/LJnLf~d($7cgyekK!6ZZY%4Nԗ (ު#ё1 ;#cI!0wpʖ!xe~W'hifv`J`\(C./ysꆯFz1AI** %cU@ENLVmcٱlt~QU -ʿ`An@TXdQe)=/B`4U)?L}4F5#T hėQ6U+|h]# Eѷ̲IvpI;-CE]įȉ]*)*)vl)S \YhQ8&Hk}'}d{y>ukk^Y_% (bs @1G%X&Vӫ<}O*'IS9:Zae$~燪c?Tռb۟rr/Lع[ -AxD}^@xDNJ3e#ן% qJAjn]5G_fN)P'0Eh%,ESlYS`Qsqr^Ȗ]<YG7@W^O -;u -cE^o 3p[&\!HerX@cغL>M}4?NGaaDf=oŷQ o.p#OSf Q;E, bFU%84L1@t !zEC=}XSƽ:qC/1!x+4q)֮ m;ax.5PKr/K{"Hil-YV#֥&9! -1bd`sw'cRpkL qN%,_R-ߒrHI/S~la+ AdZ{*Z~wKRH\q;m4zw!0#cE^Gf>^/J9!2ځo q{6H90б2r6̵zL2hžwG'AO]NQaG2xk^U/Eo|_*$- d}٥5jr}gDݸ>it3 J/ݩ3Cs% ZwNtɥ D6J ϒu^?awG8Vb{s9f_(HY H ~OOz$!usa6o-i^Mͫ FVPa&.CCv2.WJl4.@Gq* +JO}yv+Dd6M -a@!F0h!萕m.8MODrE'_#OzWYȹ]DJ s}Mvf7D="OXKCqo &pA73˯=/ZدǠY+5w8Gǯu*xl*\=@)M^{UEmK[Ra`=D(eUEJ^Ԅʷ*zPr_þiUJJ/u-x;3T||NsQ7kPeSpJ!6Eߐ}\ -|)7r6 n9)98%{&/˵!/& ^smfcB~z*Ξk/DY׫#i1u8΋ -Hnrw-D~lX -+lZ$/@11] 'PMy?ˆh'&G)y ->[#$#/4]/\,ײPl=4Q 1pJA(bzDf#8|X@oT8I;5FtydDp:A Oۻp9^q5- .)2ZH AIk)鉯wXf31G(GE8Jߧqȭp۞Rݚ../'e&~}i_LA!S50$V?l'Q,Da,d#Tf1L) @2>vÞo9 6)ӼPH{%Ќ7#jl]b9?z(r2OP*H1'K*Qy<[;6뻌iL4 -%W^l8C"7=^ -B1sp*[dDRs3^9+uui2d9`}*wG4j7~U+l!zGrP*o3r-n8J븻 e_p]iej1C֖~u0عW| ]iQ&.~W,KZcj$DZ Wrdg>Xœ/5t KHfi59ΐFEcʺrD&CD $7n@XK-VDYTkz0Wv$pu\RhOK&ʚ ayxzEe_#e1#I<;uЗu4GC~LSg`u310o00h?E -` U9x$5<,M/X^mfJ~ c_wy\R0Ob?E ?k/Hɬ72l7ğ9452FiBk -\H~زgFLaowd_DK?Q,9n֗| n )mz91XiOB܎'RB-3Z'LRAՕUv ͠ƾHcaJNjĞ? 0J} O|~OI}N^Z}}Ѥ{Jܮݿ9xb VNxĘh佬Ojl?=3lGN8^_2HE -f}jКp)">q~y2xf*=%F}^O3(xO8KfΡq{ΑDGޱ^FK h?^CжlPbv=epq;0pf=Ž)YXl`}eB/HLZsW@ʭ{X IKkH ԾS  PIXԚ>bue$=cinh`C=luJG;YpZ{=.B[PTt>dH%]hS}C犽Oee.9Ǹ+xިe)`SFDA?QJ8Cݏv-Ք*7S!z+lyM&R+aOucۢ$y+絃W&` ,AHQ9H'yH!ODhԃ?i e A,a! RDRwg|FYOaғǵ[CXV)[xsآ>AkΘ6m~$u{`q8BwHάX_Ny- (;fX+&"(T7N>\h`?~\K?YRu|Rrc,T;K{pUqh -}EIO]<&zMWnڥrlRŌW)>I0YIJ{S en$> ֐xYJ(az/GO]u4Hv~nscL -O:&WpO.f|e"jb&e=G,,'H(GqVاDDwgFi&l1z?+2Qf\VK0|5P޲a:AX΃}yͣUC{ 3 g7> -stream -xڴeT\۶5(]Ckpw'!#{>_F->sUPĊ*B@q{;zf&n2 GN.4r5rr>XL\>\?,@;Ӈ` z:TFE{gzc#5H"bdin'+=HF&֖#;S4@Ch -lfU&@MELY BXIDU@u:wUs:O?rbBZb̌`-/nSp5s+Łݝمɜ/~w{'k h0vt+MZ휁(Ӈ> -'Ϳ@0rWVQQ`kdi330t1rqu%R  ' Lwe9[:8+"`fiϞY%SQh<;z91xe',7`hR1;S{[p'jQ'{'O_ٟʛ:0Y:DC`@ ??b?2z;;̌lf  -?gGKٙ%`i*)5]>s_]mllT]3J%odkd_:KgqKſ -/G ٙ?6/ڟdѷ32%MVοT2 `Pz/31;{SK;s ;X=m -S v..W_ܟ`0 - q>3FLFFٿ'QoE߈Oo`T}dW7 `4}4}47bbqff k `,,hd$-<,v?ulC߹?l> o)Hu-?29~,#?Ǫ\?V*?2Պ_6_XaiqǴa7H@7=+Ҝk;_xw$1@lFOg9Ή ;cm[1]džCDB#WWQ8MsƜk߃H˪vY~hBݽ,ۧ"}P Z5EBjGމ4}_t>U\^'pt/'JLhG*fo֘@?"Y}Oź .n*oXt,X~@wZ!6QFJu I'S23Y ($-[χ>>wV)1`VmA s:rϹvrA'aR(_]N.qJ‹჉1Xp^;V> y5_R: -z}(yĴ>d3(% eMJ wQy8''?>ftPz\.%4 OtY&}-w3T=t]t|XGYPNhtnHՎ %rZzR*onsѿg"\dQr|_[1P TrV+Gx>u+5V9 ez,p)~w?A*eg3zs sCE=Tk s؉D֑|7;pRIZQ i?2T*)3ҜRt'otųH>%#\^B - -_3TM^!R>YRH[ˈo| ysN͈21wyEU%h:<Rc::\c#ʘ{c$rk{I-8-V (/vܔEbU!rg$'~ݙ92 lD?,\nL8s;Lᴼ"+%[;klʜ~M}6rnf~V\I6jhEh۩$Pc?yaF(5A](sQ|S7 lܡBSBGOo2}2`@Hd{\u 9Isy쬧FګVjc|NZ@ks 1+I@U)CR֠;$Ū9AqZ:b|}yMb7mEȴ0 (.?*^ 8{C5]_w24R= !ۢ^z@nA7Sూ-+-4G$}PP5/5J~G$l@MYP{2\8 V%,H75r8-7_jdrIq(|cwjϡ55|Q,+ Ph{B}b3w S%z{Yg_8au've:#a'<Ǘ"Z@:DTҩNu:q-,5SKƻjYaf]8h[vI+>sbdndDf)Ac.r+ARjM -uZ .au?E,+.e($[[Oe:##B^Vu]7Ex23lM]SN+qh]lݶ&tżV'eA@z^ -{ۣgz輜 E6MKUt?$<t2ZbaҘ3L" 1Yŋ.cH[@̭M&!dLI&RJ7voa$4a5yU#Srx,`*Gϟ*OOxC|)+F4|\ j᮵k/%n^h$n,N'~e_OTK4F;} ϔ/TkjJ"?{w"#;uq\A,ž@@(%y -Cg] o}!Bx*>3m2g% i<0M Ը3w1KuJSggEaZowˍP$A|S2#?D6ýy ђ-}Ѿf>6O{{!N y-T{jæ4'@HB\WFKln.l%{PG ; S[;` {l?kd%R-碴9.\BreDQxoN{+ A]8n+C*X=G.Q<]feDfCR2׸|LA۳͐*|29JxXgQ~V&HmH'cmš~>$qy]jU 9I˫{-yw]1\x_# %eYUHvBAeOO aXO y64$2ω"H[R?,,4IDPhM4q8\cu,G{fWHw. LSeE֚rýy>`0qo?}kG B v~5bdq QOFuׂmj"6PI^i5.[:7Op+$U܎ٖ yxO{kaރf)nk%aCJ6;>IdVKN^VQAs.px0e܉x/εuq`x:4ΊUpڹ -g[d _Ac3݈aC\] ,1Ϻs |>L9&q~N &<#%C_YrB`Y?}گhJMx'{/ K_Q_cr*x*>Ľ -$7f̆hZO! - -j3*Ӌe ;ݱ#ujqpjyjcp_GnâY]h*ΗfEi(v{snq/,x})&O#Ѿ^[$Ьv+LOxu.be_Ցbшk֙XJHģETDrrt.NNx=J7.4ciSAE ".-:ş;p`3B]Av7mQb`|?)lǵ5k}5* q /X|Ⱇ:–Vz(saR}377GC1kipPtČ~ ,MdjA9}"462r{bFT4sLbDCT..*53NI{A "-_kĘVJwAF)nv1_2YP9y{6LD([5thUW*RY^Vע˓+pi_.[d5 ^Ÿ=,ʾq 2N}*OQ-dBUGz"lZzYbHD QM}`;DGn˄nyr#Ȳxfl:e1c->PMgjJ9֤;|e*2Th@ݗͨi1p?iz*E{5βi$8 ϓթHff7(Wb*tJ U -; -TP/EU:^]vk ,s -s;Ph *w zeRd,9%x#֯pWY[kwWε[M <%:rҌxDj˫YōєXqm$9^mUfY͙De;ش[`N&l˧wo(,^:Hs~RS1*)_f9=\칧}zhB .O~cvUaO@Qw֌52řC/!.~/49ay`v1mj{&؞F8wUã n UI}$r)Տ Smojٜ?~"?ŴL'ƿ'j"|c^{uh(E񗋘ZjDI@hƼڅ(MQrJ7 )9cሬ;'0z=؛"[0Nr{6et+Saj &r{]xN>\U! qA: & Üf#SyK26Kg4RIہƴ;_i2^ivᐫF?³i];s?[җid`s УΠXk6}Z#2$TFP4s?$G$ąy46QK 87؃koC؍jG-r,th`lHzmdn(ykF[QfEFaXCJ93~qcgx4 7j!/ҭN'XL!C(P- o.C"T br;['Wi-9ɁhT%M |oqE<:+Ƙ@Vgw:hd.2!PpSD=uk\ >7B:mj'I(Z ~|7 zxCIJ[ި:bC.}J~0I9WDnC9_$}($::|.Uu2Uo(61rOSb[_G}B6p& |N~iM>g-*K$ٝNA1w"7H\ٴ..,<_U;idDQX&,jBMW^ -?]|=TKsێmS~t憵VuHfӐׄX*=uV 8醋[ٜS˾A7_@%y*x.} yk ?Э ^FMO}0H!L-4fTi £2p'ii坼d sv4mViǜ ШCY4]|\kǹ{C,%;Lb^cCQÄois.~Գ2hfdSu7-EٰDĥqe<$EؙҾ2e*zY]A'mt & Q0VsSti{o`hOO5)ǕN^~/X֗T`d,;,:ocZIo[-(1H&L@tRD@~&Kq4V[?0=鶮^7/yaM]1=T~On>p1R8P!sUcwKy> nh2LO=$<6.;;[m8m]mj)4CI֯>6/tp`ĊՓ'/_AZ bL2wOԕ?sDB_RS8`o/N>zaVl7r}:U:e*,W1grnL[;50ZPUi&ҊIBU';[}[|4Y؞^gZ03xB䗗Ȩ~h6ַ& }i*y b9T# -,L0 0znGʑwʎD>—˩ Э_ <#E?r^ -$Jn.ofs^#&8z^x2g7&G2v"ԟFUp&3HUiqE)Mӑ24VD -Zyf#/9@IP 6!$``',v=1d=4iC$B.:QZߎ!%R7ukmV@v.`߯'8 ,ƭ^/)6a;jq{ >Å-d@d1$`ci.h/rjSqo{MǪdY5 C1`5r87''Wp`kRX ,t}cS8XMa{WO+h^$ǻvUƯ CXLIG87kL_gQGD}}~u)N7Q "(wf''Lj2bYڙ}x ra9HxhJh;Pf`_FʔE1%8dT`EbG I!U"$ tx@Ix~@[JhS\C!ك*ƈ}8`/J߳|yu3T(+賎[CtEl}!1A_"C M6jNA 7I ?cod,μJ5b~i j`$&R'ƥ@upD4!`,s@G3@.O??\]\;tl KIGuGʕ&z,,$j+#-%,jsb LYᙿ |)wY4c4d\{N7=)P[ ƿ=KM[e"- -V}y/OOrWY\JڐJjm^ xzkh17/[ D>Jgg{}n5k?'92~uaY­ '.zϢύɄJa)siP.Q1Hqi#+&;#[VwyDG #k5)E [է6^N;Gـuj2[ 2^]ZbN{Ɲ]if~*Rؾ]@EBMu j4?RGԭ)E59§Q]>huehfBҺeoIV#|dShb?fLחR0eI1AHj 5J:!jc~VޠŁ ̈́9.x bt O,jU"-t%^3f ~|/7mwz/v.TL.ՔaC8|cqK5ӝC=w%P2\2lC*4]j)" YO Oi :@iSR^zb )U>ȈJxL$i {iWSk`K(ōMayxU(OGOhٽ&)N0H0o:GR>RtJO>uKU?K.vژ%f4pbAGU3e|_],(A&ſOR⹴e;i R&? -Ú2DCV/PH2w?hͻAM Z'/H:u.HA( 1TLe&\_$)<~ -d6]} E8A ZjdK}D<\Ȋ;7KE&2C rrۿA.GBNh@av.E -Ila"=E ޼)# -# L8P=&-b# q' $E Sp܄l=HZXR\D pL0jǰymrmjxZ+n`ZpPs~";Αw.8@Իdk j%ussQMĊ$`hgHe:FDj*2Z=ZX -҉*]m_`FЦoTwɸlROX Iyad"wO+2 q5:jЅՠgMc*w# qyL\jW:&g(%-N& -$2bUdo=[=vy}.z?Y,NqNzV*YRBKP - fcWSl,"(K)Ӹw+MݾWR'ZWB13`ZIŏfJ0dؗj_,c"EI -4X VԵ  -"vTv%y(*TY7: FMGE[4/By3/X'=KuΟYeD"3AS}J6m F ~2fq-sgZmD6K.Xi!9g7bqH_sYÀ\0Չ B+HM23,9dH-۾6do!0ow~tp ءFqL̍HTz⽡nɷtkm(96,ڽ05"4t~ԥ*ƢICFWю|QχeTMޙ4pv#.љ8r|Ɗbȫ;t -%ך2$VkDMJO,p|Ʊ+)\o_\64pśdqQa6CaԬx1h4Mo6U"^ VӶ2~*$Ӊ&y+mWuAֽ} O9EVO=%7^鱑ӂpF ;Wk9` ->d ,Gj0]SdS0 -En'k]jNvI)[ -_"$n} $&WWmfQmU{e%^Z60Q}nz.vb -YKNfC:ӵ4ZŸ ҽ0s咻La mOP2&X #_w?pSSk?MrMFm[2D6Sqd҃!Ce^_Q˓qO&VS"-S:c9e#&廱2g@D&n3!> -AlurE(ƌφG2\'ߢL F{ɞr OOobWfcC:D9ZQHn Ѩ~Au*>pE6 "EA\a&i }r}_x? )sG ":g7Lu㎏ *OY$ۃs7:6ߡhYLvĀ>i ̯ZpÜB[QiZE/]4Q=CYxe~ϩys"sr2p@G"/ڶt~hT(]~`Q2`5Q :Q0㗛 q>^q*b~8lVmSFi[9Kp,ZBvK>)b荞1u%4[M -lY\S5tH(V'cɍѭ0;Ζ34|@MƎ@ca+-땇hB3vL31٧ӔF 1W7X)E$[aϺu$0N+b]UB183h1YTWybךScQE="=ށ#f -[ -tTEC xt\Zx8XG~>* ,:.zãlmqjUʕXVcBܩ+6sE˞>RY >^ye^%d), vB̯R{/IXݞЏITo֩dW^ta_ְ j۶mMm۶m۝ڶ;m6w/{k \R2TR{h >ͣmkg."r5z #yHԽQ(5fg-&mAxy5MNۘ$Yc l9WީM3-v( 69BdЁ>?(FtSZ?ÓBVXyW쵡?7 -{-`5nZZM]oL -X sz#M'al}:6:&\"Yԇv8VQ%RQ+qwgRox*,L/ |663OKL_Ovhw/Z³!!ۜ|BV(9, -5>I4#~W2a%vtq Fu~HE5Jf\kԝYE; 'PNdp=J: Vo5ybd"Au@>WlТ#SNy̫C ү*ҟ!-yStvg3 WIoq{PH,EcJהO 7|b!ٿ(ܘmRP)nHfaApE #J,u釂nݸ+r;BӚ -l; 'm{dQ8yQ̎h$41ܔ:Nb%ϴJqY gLi5lrG.p(G֏]M0P瀤3,EPhfUjdXrb/@|F"ז CF)\gN?p# 뮎MA^s ~#d;V -7z|ҲOn%SWxPF -՜Ӆ&7{-ϹJ M5kʊf䠗FrzrJQI?Z%-4B,A,5P1١"!~eIng{bFbz=Za$բ( Z?X߃#=Mj"L\I]Y̺WܪnӨa= -V缻kQK:*`1ʼnlݻt31 \:̨д7 -zC7į rҾد$ylpF%;Ӫ(`PَbR.`*D~ܳf`醻eF~ӟ(9l|ȿn^X8@Z3PO0,[p'+znPY&IӲ}@Wձh gJFdE2FN5 :Fn]<r椹qԧU} Q5 *):OJѩe/nh$s#•`z=ÖM5mV#Aaݚ5C@ qAَP(4QB;ٿY Fh]je mڏL5\MӅ!UqYT >Էwv8m.$Q[Vk! 3̉ -<;@x?<:;E@z7ʹ$3f 3$ | f 8 -FbEFDAv(қW:$Q!q E87>*~b)ٿ@t*h+d?oFjiݛD6"T\ -" -\BZj-ZYB'z7u1Cb ǥ bZ̾{'t!9\_3:©{\ahLά8=xR;ڳ}$xl mZ拌Vjs}󃊊Cʬ-w nDZ7D[LLJ7yz6xF.sf4< }.e1dCa%r1RiI'*oA>Mg…>#N_uIT4}no`@ bK :|ţՃN8x.(Ta+?:[O_U0!ۊיG%xMG@Y!Lq>o -"}4ȞH}\Bc/ ZyJI~Lf֝1as'Oqjs͊vL^~5)>@ -7# 5Čm/ccI^CƱWN"u)w,\I_6u?Qh ־RMc6Xe}ߨBcPS#JN\0vҚ,Kݧtk>`t0fר)nx89@κSں:P{VwA JnJNa{܄%JΦM{flU%IJ)9N.~BFWTxpP;"zckH6j4J>Àg%>ꝑCZ cn+oa'hD95ZB._ZٟaA Fu5tLȊ&l`?AlC}wNQFRGĹؕ)<=[H6(qb'C^JY zqkh4#T-s2l;\z%ed'р*#ND+V dwFUDCo2}GSFm~9=Km.{d7nL>6(|us}'f4c% OISzs o5J^VР=6SIaBI2"SU(a<!pd]riMΎvF>J>"N4 &NUϖɳ&:|־Rzov/9g4T\h1Շƈ}9'5t zM1q!!k$TsĘ3<{.un ~mdYV1<wK#qQs -SVض&i. 31NRcnL&9|AʽwDieV/N6TQU -( (_rTF pYni UCl:leTW/F K oXTqjܨuM~m嶠UpP7xUv낺KOC[+[ʧ/|<{Yt[+zω՛N :@lgP>ѵpgFԊհv;rʏdA7\H}Ӛ!#RuK9w$ 8lQieeX7Qf.!`:2;FFox%i@ͥ2AbE -:1M$(8/Ά}LBND> #pYJV_vNv3s0r̖g -45S@.(ll -6 mn0MVeÓ`Ev(,2 =OP׽s,ϴ(wM?_Lh?-/*oK^v)K^!h&( S!W:u>xQ=$ M0D]rTv|YYL0g$*`$rJ]h=`l﵎ _ޕokX)Tn7- ĀZ{>LH4F օ7%&n߯b&\S&Fh佄؏b}otcq!Gu\T\X\gAjm!jM(̺˚BR/@^2AٸnB" -$ -Ϣ@4ޟcOظ?*Z_"f?O"إ]Q"IxU3~T)\ޝdkE8al -qa -tK΄ QEO' AWK -pƔg,bnUm*,砡 тŃvA躀$m%"a2ހrS"}q?Z$vEXCkq(:8R'G!Z%oxѤFes!ڻDdCa⚃Le3?ORZOvZHɜ?ees)#WPwuB`f_#ZbÑ._j%3c|5%)9@sZn3梈se%v,@V,髑j54gAj+Om[M:J9 1][r QW -BG觥@0PJ̺b\(I*A^q N/S뤠9`;HtN\CfP$xޅL =d|x3PD\'Pp86n/c{L[+$]6RN) N4p8#kcg{^;QΛ9eJyR__0`9aX5q1(Q,*/[wIE1J)ND 25Y)<zXAdgyohn\_N'!A|5.9&}LB>l,xwX(1zw]j9$%@ꑅA2 :_I#_߇4-eTzQ=. ]IERx&Zz+_c;BolY5(ϓ֑sr{:FݓCa_f:?łdXVIl4mPɤ9l  B/h[mJ}s:FaGִBxb~>bDPC<ԩb Ok{(^ƌ<AX@w4ktfI/Vt咹+Y+ r>pd@>z<]4Rse&+koe#"*JL\/8+I*KGFxC"Dfۣg렚BΚ=ך& Hʆ1T$4D?Mɿ.":vq}.D.;Xnw?bL dW6"PTgyBEIp a^!)0uN.Wi<V rEIT#8 ,R~5oAJWg8S<I8uncᰊ%iU, ь&}ON}a8k[#+c~u S#K~YSL][3PL47n٭hԝ<HWA g)amaBO%BD+WF=* <~Vd\M]1YIvTO҄h vA{ /Vi bȮ={3#ߜi$UL,SVt , ?+?Dy%8 JmN`,O@A^- hؿ/jsA[s@)@ZxrNk!C^Zxf7w[eDh.36c&ٟ .)H?2>fu+HkIΈՆ$ 4ndd8KX6*P] =N+ [#ZܻnnjQ Zr،UM Z4K]iZ,DH7fҦE%!WzI>A[ od+hޜE[0^ bxoB+ 3ŝ WBpmP ]Y6vIh2dQ:c$i1Dx`C hԾc?;YVƼ1=UO^jcӆ߼XSoD1}v̒?.æ`*TwZOpi :KTTf>y8WR7PI~6%q v-"  - -1~j;l^˵Fv*HI`|؉딱_?"ucJ)+3\չq'Y(0YZ?\r= +N"PvQMT `L@ԒEnTG삀(|d4o"1ý)+nKT&4dU71%!CT*FMPST4Q>71֤x98S#ihE Q<0\qT -J'E_^);>sʐIsl*٭=x|駜>0Cl8,K1Y܌>^v"&{%"$۷d8]'ޚ#P7Lj6,-oS>VotFδ|8-JkZ-pSOfcQ"/3(:yDv\jS|`5C;SHshXC~t!>&`Za -uxIZ S!1 w֋ ERo[2&^_Ӓ5􅯃!f8^9ar\tnY"m}-B/s Gba"-jO'Y5fvp.£\VƲZFR.L?_1(eY<*kܠLp[eL|| D'ӆl͈1;~Ñ.: dwab+.xE@㢑Ce]IW -C) 5kD1l\ =Fp|Bсh.6P+́1MP"VQ @6 f$PJ31d)i.= EP5؁BfW©%}wʋ u%H1ɿoD-ՇWVC ӭ2,8_ -&i"85zÔt$CAET;X):uh-5Q}BY_N79QxjxkQ(ɰP;l7RW;H LF]N!mV0ߛ x~#q4+m#`uD 02DT!`f](A,L ٚ_GV.-ԦF֮9[CtĈ :9UT. -% 36GhlV e=-v nJʚprcR7W8Q+[a`1y&xokF< 1ho9VOHޯbf#M\{{oNcoG1ƻ=_ [;ю, -4Exa_w Rax V -64Ju|;9J MS58 -)o CnLKogn_{ ;l -tb*p250*cnO=I)3]{| RyJ-[mᖶeoļj,a[ӂrR~zGyaNNo| QI,4%Lkj2=f T%sZŃ4Ⲝѧl y8*oVLnLyB4yͫ'~˽OKAik2Ff=Egn>HoWsU]<2FTeA  4Ϧ4䊌;V4Fggؕ!AXoìoLT;+&۲|=4zwƃ(,m!L/\"Syݸl#{H  ղtӻeOf`,R 1 kk/UAՂ<ЄgoS -Pۓwe18$wT@q`rXS|tS$j0YERq1Wۄ)UAa`?_H`79QTdӪXmH\[_3x/}-y($L -WoS%]wE>΁kr+=΃ilWH?k>l_te젺xVhQn)zܱyQRv]y^0>0Iq$"`byH'yTvƒF`H䡍7s X -(4_ɕՕRUpci JܬiлsgoyCy $ua0>'O5u{?>)>`ϵ^᪽\Ô]gk}L@hZ3&dx: xWu&]7n_ͣ'w#H9*Ea),ƪьϊAxNxu"kV*0Fl(Q˝ל-Z:odNCuW@s@2a~򼐭!5p:71-YXo-T'Q/~֢W v,%*/m_SYH; IYr1LH9cz\be4iQj -Ԯ~`(rE##/ω^g!poLcyıVH|;HkC~ Mh *Gf0_^1F's^ eOfD);ן?͢٪BUq7fM 79zvËl@Y&1%_a1qݸUrMX#MX.t<= ?rRDݎ`ðۈo}uDx(>I,tLR#̜Qn`z}OX;(x`G׸>@m@FC{OO7d2}/kHd@[W7iDtJ%NHfދ筼GAD(=Ǎ!]9n)]sHEEZd xn9 ye\GsVW56 9sUfƂC6PSRzqs6JnKJHb"1ی -endstream -endobj -325 0 obj -<< -/Length 15170 -/Length1 1883 -/Length2 14008 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڵuTڶ=]P\{q)Z\ -Zܵ8w+s߽kϵ3FBC"abuqp U`M#΍BC#B\ b PfF @ W}\Az_@ a_ g;gk-O .?dK.^`; -Ȫ -Puz5] [5 hhj4tԵX_ ky)-m9f r~o P~Y5O ۟=8 wݟe^S]Z@o -yyyx!.6Ӷ\ G_pzm'lg r$ɺפW;?^S_0UVWW8! gk >AVt"HyYC.,o.;3v zĀ`߿olUsasfvT$TdeY_̢gV7?$nHe\^YQOOwcsw+W6g;7PkAmigڄ?WW5 - wF(|+;Kȫ_G -._?kL^g`FaSuʁ)Z@'?:0Ô^ XdnW[eW_u/lz=L:FUݟk ߫-A`0/ ?ll2ZʒL_Q2Ζ.Vv6N^Nǫ@yMz@.(&cT_}E6 k  `q9-f7`s|_Zo]l6=fg?_X ҳz.[ -nm:ǿ?h;˖tcpqp8yyl'Zjk_Eo^@ KyK0戊@Jxj|Q}$NB-JXqpkP6mI`js>MZۇOV@@ |]VlŠnJeIdC)/q/ؗiƕ? -Jf9Zpx/bw,NvAC^qb_%g?F("~ف-XCpbK2Er A%}Myl42}=C N `|YpY(ydGEEkE\hJIDk+TH-|5\mnDt -Eڲm24mTd ;GmAH8UA%qͤlcw9\wgߎiP?VT@+Ĝ_M帑WXRPp!iQ^'h"0txd_$A(GEzc55?ײތT]Z5t19h{n p͇4!6ʄ i;6G럼k39O.pV8%=y_$ǎTfU -J;)h] 㙚'_>ģro"LH^=pHS|=q*rT!cbK@V█ݴB+pZ(_=d|dwtp0mP25%ļ [& [ތ-܃vFޖ SQ -S1_G?@بUCs /:iW!fa3+l, -shY{c} xG)?Xp{Wǀv;K,}H7LsӯO<{>oИ?*rynQ*'e䳛%:~.&)mS~@vLqyJvA)  Y{qjMM1:tS]uɪacᇐӪk`t+! "(x'#JCi~!p:funC]cAH\m KTpJI昬Zx#*^fםe.6d6X_2X!{lpF(x .ħ̇Xec/}XkҊ4zr,aCU5tD9:%ǝ:5РB3&4~@}_P ,*$D"=h/\}ɞfpN[5jƖdE|g:YR~] ~](/Af(9@~YݯD(#B JGl#f%Z6] -*b(:yw)#~aޑcݐ[9y#?)bY_:I -RѼ%M*aƔ}@%cÔ[[5m_A87Pҹ_8( -tW7_u=Qh-Fj,eUIA)ؘN${XG2;&kjaR:!ۣ8ya(;bjeC$ jIq+vg17M1elėܰC/O<=O, -zEt7j=>z9i37ǿ b̉)z -J vtbT + k_3 _Qo8fjl$4f w%Kk9/.tDiosл僥bkЖdf{?dv0L:߉h( -3u½:8ળ:[o:yąOCNغمm7%|bmss(fև+`u{ğ{+X L͌ Y9͈Q:ر۽1 nA~|'}yyՏ_ټ%ۍJp5T~·cc#gO1mz >d+*I0@_kR{)TU0YNnkyMt;E:wY=Y7 0v3(),x 4BQzbScÈM\̛[)ZMGz`h>3i}Z\W0"[jwzu-6\,dǶB:h?˰cl%N/?lr/Kn39QPG v @, 1^bgY=HFY-:<7]uj7i% b{qG7#7>Zj>0}/.|~gy懚L!u/=o͏;E;:u{߹2T[pMHsV4Qa,zu9V,(-Lbm6B!9W -M APirjevOoZ*zeunBUK~M-{}1wE͌i6&ԴPĖz!/R@H+J#앓q0/;j‘f {p\wC-.T8$fu$tC8jtXM`ȑgl$Y16,ޣ赤~ںl|տzf葙P-A(Pq~̙XD=Q_ڴ&YYXX"D -1^݊CIJKXz0yzf. [e:PNR& -[A<ظ6 ݪ)eڳőW'x#7iG8X@zeQ?Ɓ8U%zByՌ[!59V[%h]2TXܣQK /mԭ֒)o.,Xɘ4 ~9S]>ֆ)Ec9%|/SJf03a(P -`m煶 -V^yҕ?CvHJ%pp?nm-3bMKF0F؋-ď6*Hf\}6zЃB y3 Xũܢ 31K2 M.*$Lg w+k2uK1!:Ea -E-掱T|1fJ=_mi?2?iܪ_/g$T[\4?Z~lQN]Qb\ʛYxa,"cE~ƯOP@@>B$Z7Q [Y_ݡkO+Co`8DC`[Vb`#ti}WVMupu4AuP|ޠ kC] u5T4_ -< -WL0Zn퐋6\'ϴ_^ Zb|> b4{w"~쨀򓐯 +UHX,c6ZCS0DuT*]o8oysy#/'Ou6[e(YܶBue>ӞF-qg~2ݓX  ox+%{P?F|p6 n5\4*2R *D0.mEK_5*B4Ұ-*~ځ3)k!I{HW6*O!Ϯ+teC>0-Wg#zcOs[$@yQw~̭ Y۸o:zԽO{uɭQízh̯4uք23jr4tǰ0ӬדAQ><:3ndiHUv2X_mI(ԍ~˜@&,/{Dw0 yx_D xGuB ^FtYcÏ&cvM<0WqL^/]dUAo5LiF*N׮\њ I$ :[!h~G$jJ݋ҠcGh>;5ၞz$it%9В3btzQq>Q -)jfY {01p#e̐8TGuz:X(/X`?ZwDdv%2*']l;Ԩ.iR)̋~lPe_]^ :$im[Jr|KmKPZ{;,+`iˢ<`huWP&̩ -Aj7]eA?QY XH5ml4}:W.R\䗴ʮn#jBcbGKf i*<,16&:^&;$К"|_O ;@2e St5\ $8/0nUA*4$Љ-=klf s[Ji;d6ԜjBV&+Σ<QS+.4r9!e҇p^H~o.N|t~CjGS cU9PPl'6yR(g15es \ܸH'Q${f2c -Lڈua}d -q=^t (p MtBf!p}K{*,Ǿ05PKz!/Aڨ3S/!wNFsIREֳ4Y^LD+ 8P8 rT0x7aL-ŽDy +M™B"cV- 9iK_TVht9N􇎘~ diqMd0sn7پ4xc~`YTE̞rirr$ܽu6.K屿g|#1!QSa-^%꜍(&$bJ^}|T{u<ڶSpIa@DSv)Ϣ.Z\jλvT0@h Bc{S6r2=m2YQegK Y<s"O5&zcM65!^UtJ R!=P jxf0V~Tދ)w{e,A{w߄K$o1u~2GKS-F1¦ݬ7}O.BKKB7f&&a?4#3&!U+& pTXi? Āuy* XSzi*yb 9] #ML^R yB28 -Eo<=fNt&4zfmAi`˄nXٽD%S6p2fS|BNA'K2c>=qB7.z)J`19(x_10,Hr̈ᔦ1 `+{WƷo(RǺo'$g 4S l,"0@sJ& fs)(kۼT9o2w9bNUJ Y$=ÙX5"Hn#B-@}W1iCt:&j3KK߻._YY& Zt9@׼Jui~-O'3yxĥFrbXi89ψ>!en5ᗡ -"x(;P:[ -s8iL:N8(b oߩ @ χAZ˜'~f'4@Vs"Mm7z^+`oZ!eeq|^)&% }ˆkx\t A {”{Dg je+rԊbBi VNPڕwB^eflfGvc7XQi~4w#\opoc]%S >u~RXchjj2 a zdbAr4SH"0 Da2!G+B/2 D#SaUkC0h)Bp7{PVqI D=鐒.Jhq|+fCa#913=zwLG8mKWToPk0dϖV -4#^  sV1vS+01fO^ֱŋ挊eәD" t wq/v2 Ţ4SSF) _VDpBp:.ˢil%{۟WØ8yҮ]ؐ*9?ѧ+i0䍹ګq4뗊_9J P EmL@R#+iG%dT"y+7tJ#$f3bCEG] w /nx-OП[0֡́Sĸ/kzt5N&|k| -|HhtzuKe-Gq&@jO͓o&"j4xVcj/7hVo.U5\t8Y!HI| -˙ԏJr1OE?Cr?r7^<#e2m8kM]OlSMTգ3F|jʙ -SԐnl5Sdd[bJQFw쿜ېS>+o! Mx"-YM^+w=زרR"iB*Έ"o<=)$ uWi>_Mjv8 (;I#pA^c ?DewIPQ4L`,$ˈ?,8:k#?;{1"<;_S}i -e*pgօgOJpc2FC۳Y"1j]D:x~fTKtҘ7 ˿?ʻ*r>%>ZLboH eo!)ӃP[C0}n1~'RH>eբ2}w<[O}(QdU-L΍_&Y'zUpj>6l wD2n7ƉU݊èͷ^|#?'IzشNcBm"ɁWބ V{bSo33w%h߉\@;Z#:nq2IVQN],]GwnZ:/951LEʠx!s\ٮL V68xhu_1K+ )b&M`R=||+lr{:EvW9bkrNgUAŕ]E9J^QŁ H`ӯ`*E.A0{ )t۬7XdYyj&JDi[h(Bp2T1s†%tEu2QNJ wq=iy9Ç%6 8y-{;(bhj9#ۼHDG~uh7+?5TV?G"bEjN#2s0vرEM<<We6(X&|۩yh,KV٫@7^eCP}'nmK'E*ULxצMk8,J&ňm r*fxպCv9a>$9^Vo/ai;NYa_J֥[{ee7rvR~ $BR:v 9",?E|Bsm$󱰩Q 1틣9C{4N4*ɞ銙M\5D*L1 -q.KyD -0:XDvQ>ReꃍI_KCJdߣl+Yeb80k\0V!b; -flZK+(D  CJ !`[X9QEWA;`6*eh op -iZs0:@\ERKtqԒmn]&]\eOO]uJIUDوى?~Y՟)>WY,5#<4 SJBgw d# yÚ7kqp5O&6P-pi6ĜHJtFE*E GcZz]6ʦ6]}w: -UyQ8T;ήB?u؝󇪏9R͵jb\W#O"upo=v#^ewx1-zv3[?^Ŋ̓JJJ.a1[*2ejXt/:䫃7UY臥?,GWtD.uة ƬܝN^ܨgezv97|d[`kImɾg$H8ׇfY@˝k>٭K6"WQVlX#EzB1^-A_,t<U|Wl>5\+zJǴLt,QCQU1sR&yżyƗ{O>0{TȆD]Z%)?KATs0:@jCдH`Ig9ʞ4id%us -YwTd+„'&К/!QWM! ,z4 II -˫G(&ޙY\|RemLᎄzkpI;'esЇ0Zs];+ݾ}ȍ@86s;V03uKƣѤ>`~;$hWWG.ʍ:ײIh_G^gb6C7 -endstream -endobj -326 0 obj -<< -/Length 19100 -/Length1 1730 -/Length2 17983 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴcx>l4N&IcۘI& ۶ضjO`?}}9ֹ9P(0%lAN ,̼Y99[-2̎@A!4r9y\Nff -$t0r@'#Uw; Aщ [4!vNs107Z mdbehe 0JK-` 06ؚT5qe - Gbg;;["&IWj*_Uy:UUYp:8Z-(?G?NNvLLΎNvSt:X> 2h_  ki9Ihʏhߜr8?#Kd2pt2rrvxMuvp[CMS濡~L?'frvc؂-0EwftrR* 1~t|bT?H*2@}b}rupgOZ[l]A?j3K߾:1,흁RbB_9 n&LÕj&x{̌ޖf7OG# +!pL-M>h*dx@ߦ"?kJ󱣦 kw) Iٲ%lm-od;/Rjy[#Y:JXM-L,RNF[?&J&YPڱ{kX8F+ у`L2"rtɗA& s+'O6a dsv: $I ?χ$f_L&lWd09#3*]{Y -aiqX> -P['; t'_7?MlM%7MBS0)sG,{(Fo+W?P"m%bTIhGY\x4KQ- MnɯtVVLZk\+@mX)u:Tu-ڵ` m i 3&ʨWxv074]O6]rqgJU0\u 'jw-m 3q6H*\EcӂP{8 N9ѕV s=P70rݧ^5JSnpovc3/ {OMS^8x)t./*=]:֜!e2^ -ꆄ[Tv E3wp΄7GfGCw~U`_-y9KR'ZʡFp w{E#C ۼևGփJ%\7h<\>:>[E)nw%qd_-r]RRIH g^L,Ʋİoyj}IY- =I&F!Uv9/d,\b/á X7". \ jF%!Ɛi| mW~mPDC-iH7xбCsOڇ1q۳ _E[2Kg90sYM=1j40pӀDew,fkDGIme)8G:(M>.=S?V5gʟf=L4ͳĞ(_9䤐$ Q!ٺq_d'"V7,Wz5xZ΅DB,ɓu۲eF6s% v<;s/cEjZR4iI:m32by(O׫]vI^1QzꞴ2ws[ߊD_62=t7ݳ$,`Fpruc|lу,X [һPǽ -< -{Eݎ FOwdoWMDGrڣ{󘃒mdN{q53ꚁ u$W}}5B(L b͔u;`=&]/u$+%{S`g`bN -j>$K~tT*Fڣhf;#K\iY~=6O?Zc?`ܸnu/ESW lZ*Z؊>RzJ X+;^/&97N0{2f - 3KvG&. 68*(KX;cd{e%, hG }콍)xfQyr0 -89 .e=nXi+Ń܆R?wiPTYiɫ|iW5|O tG(eSYc+`2!Y>8ISjHPAe9m|eH%M0@;B?p,b*LQ%}flw݆R$9ғA^ {^0O>ىɹ{WCqP`=Z-''/9zg#LTr5eul5a\^'| -]ii3 -ܔOOS5?7]0!.T^_;XSЩIh:e>e/`Rqua_i1btC 6QFשq]'q|Nȕ{^%B'v ='M0A}^h&Bȗ<'#f`9<wӾJEj֭|Wcn'~-1+S"N<:7&aOP&[&S>KO+adF+/w(j+pa$㴕R[Lh-A52[K'~ .43+"`9vR,ߕ ( \:~$ܶkRnᄫLK?}@5&d;d,5^`s714#9qm% $7mAsWw)>5՚u@B9\W"ij{c;N9c^*:˱:Dmo"ĔݷiW8}^nOY.R? Vhz~eZ\^@Xm! -0wI屟Pmt96Pysuj_#)2TޡY)Y^Ֆ -$ԌPa XC\Da1F ׍!9 WD k玾ɛ -bIY -QYnߢ)3&v g2'ހH0FR#mwK>Lʷ r$K -G⚌ t TTjӤ^ @(+XH_ED5wxK6E~r^ a>~ -4uu3Nj[?IRyaK A˵ |jftbHxn ~֞&Ѵ k=qYNNjc劏݋ !Ž+ -ηR+<. _#$Z} sXehiVOƽݗ]y -e.+£| I"9١Y[#p'1axy|vDAi5Q@LUQD²lإ?}ᗹfKuQӱSM36}s3*hafBc0OrGle˸>[8xb@\8HuxuESQV _9[Lmb`.iKMϟ:!`۹`t{\JkJk. -. -_O] ̕ԗbmx3M<ӗ{&ՎU.| "Yת!$Σ R3&Ttƾ1z3$[zQ! - 9*}¼]A.f|24j%?DasW2o"2&(x6Gg'4uGoYNG? -!!ti$#uZ,Ihrfzw[Ayd,Q$[τY?*uozi( {!i-;E=d$KL)rF\\ F(sݷ M][=GlߩFT_Z&j>O.)i7i[@5HA"H4a5=(68SM?E0$E\)t| o!V*d97/ Z3'{܊_YoH0*hq`[!EtlɑnxjL)0o+pG"6L1)RNIMuɏĭ%JKnA}lrdC<[NbLuߌqh˸uq?t*Kq!Ij[: N[@3i`hϺ_tۓvolL:8ݧX(@bSW ΕNI)b'*RQC^vu9aQ5:-OosX2/b#"^RBO#xk:ge>vxΤ@bBX=q캤ᆍk/X12(ɲ8[1BA;wy<:1'&Ei< 4 -#|s\: -IiTD8eSf/U皐8/Z7ʫt([֬UOot))㦇R ؿۇp٨K(`*+=MU!^BjW4_Ra"<+]!StIw>1K@UB_ IxxkWI3=I]zfKLjdiaE?Z+GA -h|lȴZk8[_ɍEt#Y# Q8 *[^sBS-o kMQߟ -iL*}q2 {1_p/m>cݤ n& 7SIcj1pޒքxEzTj@c)~c׺ a(o=DwubUWz/ b"$E)qyo܇Ǻ+ưϬ}1Hm$m`޶է͚%skid-, -l 7cTBMBJg%"%ZG+pÚŭEMuUQJmYr﨩ꯖW%r9.iDFJͺtUϽ~#j<,m䤎n ӕJ*Uzcv-3x#Net˭LBǮwVէhT[ PE+[F %uEm썯]UȞ --mK ݫKmSìVR)MYxrkbr&F]W+!M %`wQu&l)AǥzA){S0{7))[!zt1"qŤ05>PB^HEseڛ^ms(NДIs -"𭰺,ӡ:@Zo~E -3I *S٥Tb:nz"<p}Qۊ:R}V(}ζF2Q m`u0$ C]*qj1C\HP"}{NX% Yǿ~MN!%J@) -*,swdſG$ ݇+U?/SD,4xo@hj^dHo⇶OiGa)$wƟ~p5x >Cp]\gLM9 0a>,O|*Ȋ[r_N"Ga,$:Fy(45r# [vA`Y"~KSHx{>lbZOQ1PlLn~5"hjB -K6D9s Ch^zR+11;ewmw-r db+ b.L>pidY)~Id]}uc^7#9»+ iOe"_^WY_fA &ƒ^LAznЖԟPsn+`J3Gv@:++&ӟH"AXx uUiDɟ{I{m^zTODEPIb4pݧkui}-J[a]Z6cBghƳ,dfW4{1yp_:c ՘'niPb|#ZrqCb!٣:(5NJ=S4a!W"XD\œ[ 5;LdLSr5)% *p񻷞AfG=㰬& ZUJ(!yazlƋ,Y]E*DŽ1=8hd|%=划Vm"7OS\Wa=<-;ek[5+^%ODϥ`QZ9lhc+*v\͚@2Ҝ 릭̮Xu4/5; o2!)|39^_K?V 5m;N¹zہK_1EµHG'7v"l T/|?إLdXkĭMk^BVA\ '!q@r6 UG6ve{qgTiвaeĚ0hnʾGBǖ}#dUo<}uވuhx$bN5|kh|H -` E2E+S{bKMi}5}ȐP58s݃]  cm-=^gCǾJ -EVrdTvKMm ?uRW^o }{nZ!2vl֖1/+V]:S>5>I@7{`QW`Rׯz5)"*=q! \Qq+G1KA"_/Ւ%ɀ+Dxѷ-#&<׷scBele5_s,-\9fAa_،+l -cuYYz,YYi~: X byUuS1Pxw_}=17x?{φQ+<j7zrUFs:z&c\#k{KvmA#BB*NTH8hi=I L<2Q?_Nq!XENL)M,(B}Kim9foMaZ0P67x^y{B7B;%R -4v}ʘoXUzr 5iznt!<~\.UT~={k [#,.9x̝-$v?WnҴFK{&j)[,fY6%&R_)V!Gv}龱:K|kpޞ}ۚ? T~"`lp}F *Z3oEّ^N0-_LеySs˿:DZDLq͍Ekcre*Z!{ Y>Lgt]FM# Y QٮgÜ0~b\M+j$H6#ZD͔|J<5$09"Ad@XD3V W$떣"웨+ (mV<=f$9_&i+{KqΆ *2\ v[?+ZhXRIpj-+\R -(]`vZZzgMLVU"0f Wvw.#&EIؕڝЁC~ 4o.n)K(1ޛI8Y]&Ф1}Cۆ07ݍ!C1*R{~hZ)~;0㭚|]"?[s|2o~A[— ^%uT#'.k(&5a )JF5El#&JRZd^BՒl=,c-DM0!~"i(YOL!>,%3RHwmnW#aww,f q/UTPx}-iz~nmO7)YxNƠXK߻LB ڸ666- -W-0 Nٷ֡6Oa;RBЏܤ:Rt)l6wչԝ dh_P 6G܋;qfma,H/N'0B4m-kEQN}~֟43 ,i 줾oJ@thb="Xz_`c2:0rLlA{ғG jM´h#i6oR"J.:WjBTї -^ %Pu`upq 9„O ٸTncmz?E{VL!9' RC/Vjˑ~ir]#V ~ -X;r!`َ:<Y0fd n-Kzkmg5mX.e1 Aя9VY睡vIN ;М9,|b́Gwe잮H|Ѓ#*@ $фť=LU134?S-.s-\E -$[k͸ -|P[48@ n-Zy^]|{h-wb-7^GmkPN)- Qש`#Iղh_0Pg+ %UHgk Įn.>:`a]0a=g T9&<6 dL.1sYlR)&&X"8\=FFjś 8}K1{D!:0W,'}jFqoGi_xf]ԉfSĤrR~qNC '&1ݭ{Uf2xbk)#6_%N_h/~'I7r]z%by-g*1d$nDVWBY![8~\t7;/8W?Rgʪc`E`kό n]w2hٓԠungd(JoITFl^]}ot -oglS NlPPc(c.27 @n9k@WB]?:`aA|ْ.ҧ?S(1;+Յy{uG)['u$+`!gwJ @^YY!T./-D4]uiOіݓWU3l8_2t4\H[ bUT6:1wَKV%Wԟ'hIV+n v,4N T4fHwe$m:?7aӕwf*p}K -Wjͻ/ b7狨5e0؇‚ '6J s&{ iǜ٫4v98A V$xJuc'ow~ a)âKilpE[\b9uIoA!@>N -w'sp^?薌ýPpWUk2]va󓾭/gvzịҀI40jE'o%ts&> b$xI GT$_XL^t] xV،3IWD2zkv:~̸( \C7)5q]iA\D"'׊:#:"Lp'n67B]auA2N09{c`ֿʄ-05GV]NADZ3$= J Z"&ᕊ(91&BPzA``Ϊ NB:} B~%9nՀI!<9꘩tb>L4-H2YyD8.g{~n}^QM1F]W%B5e7ꎕ(]Uxp7z7P%U"t=o]֭ )G«:3ְ 烻h?P -8^'¤#Zi~oG/| XZ iл( |k ?ϣ~+֬s.][ P.^!˖WӱFZ.4`v::qOMlfezN*w_ Cu֚5}/3gH ̳xe֥!/u`Ҿ4E*r%Ü[dDpڴ0fc?3&/tRC)@$Ns68 wEIoEp8ߝi! Q*)(j?•s(JoX@k 7ĬD'ъKY*K?d. >"6)w?ޣޒ`,\O%ҚMƤ6ͯEL y@%9'aR RzDp/C[^(hEӀaԏq%ԅץ}2}/oL5F`FLgzrlT\VH9VGn,nii6A 7[`7t\%BBAC -t҆ "B -+A.4 wj@dMK/QO)Z'%v^}1ia~Q5A©u<\n3{jx/LyP*YL}z>7AbQ8aqFD,BtGjLQBgnN`,,77 P ȤFZ4HfY\ #"g@$ bjt-鳟ٻIF>C']CmÇ)~˱NS䕁p7'%W-Zf9W@$\F]5H1YKOԤ':єgFB훔֎e9o]9tv7604 `^VtzkWB>+ \Šj 5Oگa.y:$u>n^(/"El)0Zb^V]NVypUt+;3o@O?HyLSwӔqîapHP$" ^<>UI|5!s حbzɵ -uKZ-rC 0cQ8i0ծ~4dќJAQ7e2.%Т@yAR "CeI(Sx#„4ȵ%⧦\be({р̅_›7yZ]_;6>_cbd2䐰54{" &y Lak eD6T4WsC|f(g϶ԏli]T&KVfSʈW?鳱=@+T*PZ,[x)`؟ 4u-i*[`\;w]/lNEH"H8]0:Oess:I -sA[&xV3Z`Ãz nB@K1LQ)1+µT PGgf]a= +,^=5OmE0^#fi*qCfUa|]ݗW`2ܥ;J . -[jt^F4sڃ!e x׍Wאc7sz 89/ƮM) )0}>!s/YJrr"ص~@+aD7\jHyٮ'aU@aΞnW BD9#ϥ`njC|T\mU i-K"j ! IH=hA\ֽSha-EZ?kO T5UsVV;k ほ&^IB 9ͱ'󝡌bhg wvĬ%gFϓ- WNAց; `uj!paT!.o>ܥ֖{Q |/iC[*lPK3Vil|ۋfM,$'6O(͍:+8T>_%1=D_J1@{RI5< `8~״ⶨLdvr.k)`/"] T"@՟M@tP}uHᖂ -iZ nnj Q|!̺h!ߐZs/_,WOAzVh =!5Z`E"PK-JJ@քW d}3%R3s!+I2`5B!۩B | $x/ƗTӨ|U聊d???"~TIvcsuPhpSU!T!2Rō9QNMLn-g:pWg6l 7/ \m~PWx,\G< EN=&݄3mF}2ƒX.E;)x zt -qzP ] ק&?l^eM9I&Xf wV\P|M݋[M(ϗau{T6̢*467;W=c𢻟? Rxg{G9zςR~Zh!9_OE ݡͷ_"T&dz/'/l8)$Xwg@4Kn:Ԡ:E[G:;㸅ڇ3Rs\ -HkaO{_fSF^; fO-w5]U[QKW0r\GV:k^3u_awjw0?UadIuGs| Qcۧ+*hy[i %( RuNkIϹ3ZY$F%z0.qKLD`/ [ CO98Y: Vr$XZy//__LFVվ; `GX]$t660;ѮGM5Y7.ܳYycȵyFmZ27s/Ii -8e<]\f]^7=D -6_oPq2M+FhCP$2q+}3*q[FQGM~FbI#gzJ+C0 ]p++ΈB9'8["AUkLLݴT|kt.եמz'7pդשn}naITvD dB@A d=4lӉ̻ʹzĞi^0,hzd_*r]~`Xe'|7X([@\0ԓܗ,nn)vʠ/T:Ú .MeK0frH{\mxCYŽ@ǼD^P ;1 ), -yְO85`^W:^aT_ZَJyFO,pZ~ -yk8A^r=gnƹ/ځ9hm'(e: Qp >j[@X1I]tT|P5>ݨ5爉0[ Կ|*b3Z-gPɓ*SͭcmPl}=>/h%JO kNvh zoܯ֪ԨI$uȏuP md[cqqԒ׽wSX?[w|9.N_j^ͨ)vyVٜj0Bx-2&V:(pkS{}b=ͮmiFq\UFҚAAJy`#n1DZ$&;󞼮ۊ0Ka{(~bQd9Bzwk(#8v>2j-s,k0줮ZYR?lk4b:%HPHX8vY>G+W :ݜYS8?@遲]tQG^i?>2FWLE=_lƁlĹWaGpȗ>쌟bWpo1e,wzci~q(P{&߻p 19$f^Fs'6(k+C],8:+ݻ Z[ROb %js$hOj!I]D%"^9PK'y;|,< k'I5@z#qtQ(#Co{*)%lwXJ%EMBSן%aBksEUJJ3B̪ă;pQ(d'iHdrpB8#Eu4=]7X0LJQu9?Jpc1OB -L݇'b> -stream -xڴyuX6 04Hwwww0 4HwHKJ# ߸9G ~^zz -5MfqK9H&PRր8٘5@` Ft8J@^74ƏJ9\NK7@v599Z:)'o[k_58U-PZC<]m@GK2 @ 5!s lX@zmMi M& _\$5eR*Z@V[SZ G(k&4W4;5 W_m-75h z77'VVOOOkwW75/~Z6O=%%TN7~ -@ -$T_5\Ap:@7+/ dI7A@W\% Е}c@GwW?- nWl_]홭_6eqyiM-f%92+C8y`$|RP .ެw!aehiK{Kw'VmG[gwԿ¡&6k r,lX5k^~B:AV@+ - -\A:PynQԿ;ZALuTXPYU nБs;tM;` g~W8uYٺY-vy7 t euم?/3;ciarup -_ `ԕT`cWQ٠ e%a8Bܠ)'w7׆pXF<V߈7JFV ^6o`8r'U7*FP.oAFP.A|P.joAkFڿo#7: 70?OPAb0tjmeqpݐ B? %? pw]ށ‬]~]!Uv_-݁?X@kYeY. W8? tݿrA`dr#jBd -'[(=ߐߕS^oݠք# Fnh' bS{8]8r!nbg*P~ᆒv9sŀ)>j,7vMtkiy=$(V4@jRkY]K]"G&滶ʏJeasi->\}kO-AG#=ܺYKS04 !=@2P:Ӆ]Au932v#VTi4XU+Vk,gv xNʏevÊ Q" VNH/_!m쐗^}X=jjrl"{N7Rʓ<,H')&D`o^4`\BqEONƅp<cuAw  %qtG NBp(,Wkpv@y7rcOq _?`)TEO$?feܧq߾vHx3 CBT~RJw\՟,&u#])tR':5Ujdf"mSVM QZXCůz# Kϴb5y2hLwH%Zya[Շ&Hjivy#tLْEE;PG Llzٰ#]Qb"]0Jy!11CnaLCjʨL~-?IHͧ fGO՞/tؙ`90 u}gtJQy@_:'oB4,N./i)#\-'sf"4uv &3Rގө O6, {.a-ߟD>0յ_w u#@hjcm蘝2Yf٪/C ĴCVb;?9ү;dXmzĄ`œZ.?3LLX*QgGD#;bűP2a5.%HĠ:Dk[gx0:/7{ -{ިW8AD;lʖgڋ=*J&7\T(YmN  5,R.]u}3 "݄dLz3G|!DsՐ:Jg}lWw4q +}pQȃcQ2ZLֲmt:PM -ar -Ƨ&V:{'aD2I2 -.9ʔ?eB j;] --ܥ/mʇp0qWYUv| -Vjb:Ӹ֗.hL޶i1Iԕ<Ncp܆N;YB`]˛*4ZF? 2ɞĊ6;(D*2cJ5!a*]tJ|6#F'up "#Qܽōt5̖Ҩܙ~k-!(NR3vN։YrK`[TGes&.bb6y7w|G1C^wg Y+qKLO2lMnvUa2@$ zq,𺥫0EöR/1j;x۷l՟ =3,p_p29YB4_pkfSwQbHnE32f6xoT}1%oM%b$Ak7 }PϪǟ9$>Vj*o;yxm%U4Z6?oOq}sdfbYCϺcGsz/904N(:я޹px.i3l7/=O#}B3+uunfT- ,҄ʌq`3Ѥ5KWH*?93#qdr,DH|h92y1]bNLw˾TFpK!-nc3 HaXDZ vMhYSr)q|ΧC$@Ó dך)6d2f$?Νx)jAgg+&?n]F  X+?YsTIK>s [M+w1E,{Vx*_kأ=!(v [P};0hm֬8j1DLjDp229^6=: +!0e&jhS,Vd?}yt(q5s:1bG{2NFFl|){ H78QzN @DG^7=b^(Ϲާ\ GQ9-# wÍ=v1&8t>D/9q!Yz -ߧ%](jzkoYtQqtڤm׷a3VaG -ή(Ho28kkQUTPY:%=V!xZ nT;]^!BAVS9n˽Q++9jȄ"W2N6U][ES'oZ}#/DX:Uu_MViIf)S)Nqy*YZt,zwUnZ(FgX5c~MXQt -Q=/ts|\/BSjF9VyKeFA EPl6,v,!~h18 -y|*W+*Ds>ޮpmk:o4+1pк&ERwKЊ/}43SV10/\c~%Em&d 9 +O;nM;݃-BX ֦'Լ 7QMNK]s8faz({_w 6eDF{GM H5;0?-_WO併ap: Pۃ)y!)!FU ->c#CB fWj 亟yQ8 3P@Hr8ES9יBRW[TQnHKSW|Q5{/ǟnOA_[F y3BHm Zݠ f ( Lg'֩EGT=w2'[#`~7gʳN6X2UA?7UehYU!56?k7TZapNHlk  +ᇕ_bh:G<&L+m'uhת fwtJ#0|"fyE#0FHVN֑xRP}"ѺD. \{bY|O N[j KkI-G1s}٧`ξւlA+I V 3#0r -}TQ.bJa!8X~B(nx3M o{ILžSSvRb -/VQZxL0qhX҈X\CHA٫K$x5VxFAÃv-jkWF1Hf3"W]W7N;dMP::5Z -Moc܇_4hz&n&[p\)'SM&^O?/VmD~QFsAS iQ94܄;k-:Ӱ7>WjB2mܵ3o# njR;Ƀ|@6hr"fk}@k \eg;;+HBSL{-'(m=mA'§LlY*Y'4aS"ע%~7=bWSHl?96ur Ȼ=J |6֮>Cr^8.98{v[ Hx&s:&g֢ "D=l#qlnG4}x{Tixx7]#)n(!(TUN)J{z6 uwwq:-lQ^׋N''lנ=DW|=^H+M.C:ŨK'I<{>niRZȑt u'# θ/rminj.*]IXTSCc¾TaۤXT6\%wk(k/담OJO}HߏS.|8$0{zq>Mt2+Bdc|  $7LzOZ0}L Z&"B};u\th?\zCo|9~VIj -l1lj`x>W,*p \7|JE`=GJj&FH adlV?sCTM+ (+$m#qѻ6!UQ ->:ivqFlhF, - SyCJ;iTL%9"|ʃ8p*fE1~$,ϚQs546␄_D\uh/HUMy)C\в#?AdWpoJmcU_/V RRYh W:$L|hʡ2s i{rhH33`\/ P~|q{2k:¼t єuf+8TXkuՏ"gbç0`yok@W]} k~7ȼkuyrL)y9˂4*^28`:]<ɛoTJd]7I7̟̭;^\'׬Jz왶4^w )H -ϜVXSAB*R-9zY0 öͅ]X -n$൭ͬ:3'`8KOƗh47>\dw;Ν laƄ T LfmKNG*KpjaJ8r%Ro ۩vK0*|3`9Z뒱gZWq艖&bBd2 HNHŲyqb?l-#)cj- -&Pv6Ƌ҈kCyi[z9D^  zX嚸$̹$Ek`=rT/]ݲ6* q\v,z!݈x GV_S  nnx=FWA>ȓ}MO!cl6{Ztɹ  KM{F{:!R;Fdyt>LB'"Gs,D -qa{iOnEYxɨg-r@jUIlsjm'1h3M$cQ-l创p jL(@T/S4 hmoTYCNx/f(v>9q~gY54`Ȭy@Fj&7OϒTH&YX r 0N[g-9u1<س!&PDрј+9LB FTnV~p6Qr[ Q`O`! -6vqS]){W+ n&r8Gt-JPAY}ct\Q;xm B+A)m4m78ٔ@?؞H5ͦu?O~_/TzeRkwfYydǚa^,S;Hn<) 8joZx˿?9۞.h:6=TB Nܣ]GHDZݹ1<<]c\U2c׫v -U]*NDDsv6y1׳Gvg8{0U"YT$rvFj||m贄};ybIX ; 6e~I}h0%4 GH&ʝV8l\aYM\jq&_[6:8XZB\hL߯ Z쉇7ބQFd`\vςXC2 - i3WDŒ&4*bO?DX߱bJ֤44 ?EE2*LQ%M p)CS h ڂy#ΏŐڀ9AKErSyZhl1^$3e\`/ћM{=7lᜪtXA5£HL|58gD0Ā׼%#_`L}Bv2>3׉pd>dQh"`\YY]hCs\{R7/B5Иs)XI]$!Lnlj@w팗*45~_D怨u,X\{DdxUknHT#aO堻jV8oSWB.!6xķt\|,Qy|zܔ%.jRڒö4O\QxLm~pԵ>lӊ-{DwGMi3>k`#xVtLi*g 5[nאhs:+j/uje[W1m6 -i7LJїl6Q0IMd'e74 D{+F4;~p.` ߫' XۘD姜1{Y+bGJSU&j48Nvȡz1+K -#K\[@n!9R֧׉|8GUٿ?.>df6cY-22^x\ʞeLݫL;|Vj7c9mj>[b`wO[8'66kU̳kgx\Z$!$ğxw*=_C,L>sWD9riOBwC0]HNnZʜ7nWOzjG[&Eziն}^ܪ_ElZ Yi1x~!8; ofL~xRU.VA4G1tzd4>ԁ-N 璨ؖŀSC$o`BEl#jP(W=D$Wl W< n?2J+e) ʚ%E|Cr!D-^38 -^D宜(3Zzw7󭤐)jyk bZ><5^~[2#ښm*VZXy %?9mE]|ީ>VJ6%Ua!"壸7"DK9> y^/gdhhj~Icw L -(s¼қ~#$4OA)L2pFܖt+عA\t Nهo$#ry#7_&0S7Ҽ#٘<[b>g_խ2a$=ɖ2j{Ҙ!_@mRSQř[ŋ>Ѿ}n% y(!cfqIff-)a0œ76{X4'`x+v6`6jg"oHnJ,+hL־+ %e؀&%\MHt9SLyxUۂ|(LXzF-gS ʺfXUW^"8/>Ue7LWĄņT1HyU%EjjkpEn;CD%6X{T3/fTS8ꕧ\sx#}<O=f8Ү({Oҩmn ;WxS`>%amS3䫔POד$h$%%X4wLyCf'FXiK<ٰ 7+/qy(-uDj`ږĺ7p˾M5 rr3_A+4y/I@nƯHƖMc؛šN Co߿zOzvEhK?%zXeA1(NĢv_c u}/U/37G Shk;eᙘ3sR]eEHJ] dwN.ZǤ֔BOTB>Q oCGF/&JV~g馮aLɺ: ejˉ.0zz(ĭmrR ݠJ@~?pOKsp'쀉%#HY>bq @k+[:E[BT0i]"&*3 -XY+Ecv"N_!ۜ2:^|Qъ`ᵴAc=pƇg"G_J9w^T3iH $:QҪ}=X}/]WxGwE{ t0^>xJJnlʝ˜H/VȴHT -&Y7/,8ximY]Treݢ -#RPX)ඃwz%~NeU.G韶 )&3#atȝgg3gHi ̕޷jae3YgΎvέHH8ՎzmTqoƫ)bϤnK+~ (^icJ|ꋄs|K wϺFڮJ]0SQMba[mCm|1!D>!MB7% 7ssrAYN!H3Nl:(:'q!4*tUOoaАio:SFh,?@hJg"Ґ_ -4͞E:lZ$LeA*Y((QhIrtO-}_D&bg}wr64Ia_X)=Fnfj9یN%%.\t=9s+bGvvVlMѶ0%#V04k6g#='訪b0nvmvՀ sAubIid"lCˎ{EE]PS$od(Oy{M+jQֺ"O^½ikO%.],[[3/ ?p#,aU3Գ@.8(8K.+%uEqۊGX&iYi%ǿ]Z]+ppܵ \w@+)+rSE5UC[N%n3^ a:WukP>b23)U6E}\!My}icW`P[}dW_T^f4Hꢲn^}nYCނɂg4𼢂4z3S/HyF?'>ǸQ8}mD<1 3R&嘱5TKILwSR=kՇѓ"|R%iٌ֟+6*%`)Lt\qSveiΜ:$1EbkRq7"[[iD}&U3YM>a.pR=vj](˙֎8j(C#-ucm9ɞKѸ#9Ӑ3uSȂ"+d*kq%%~4V}E&8ƥi]r_gC"}: rM0ZxF3Na[\5* $O}9V>[sq TrQ.KhdY$^M)l -;S33޳S)4+2B7b]#(ջiivPZBNH?r;k.g3Ѩ`5t`וn?㲻jkdH5G׳-X_SSb\k&wH ֮"\1{nS$ 6>M۟ /.PCod_3v7^-Z?,IN`^/O5sT -ғĉxoq8u^㺒a^WSdehz)D%|C(@Y -b{ Q3 P68d@8vP"+eeG/kEqCVaT:︢Tnh:RThP IYg*9dxƬ&bƝ%ݹsRR~NQ8?NjB3XJK\kP3C|7bzԑep6]p:^Vmo?[WV/F5@D0ֿp b56]^Z(d@]\BNh;kyg7%r?z~/H -D;Gj [&>ezCsx,@_F[H`90+SC[YL,14zAgD\w8/8ӑ^P&ōr*:N ICwoާщKi`yXslXFfD8ı0pN`_hԒ*}G~{`wK܏XkܪKi)/]Rs1}ƈ=;0#cZMbg%ø^b2taTP'xÛ/ƾϥ >$& Z bf"m C,0Yd!qV%LNiL1ӨȖ8yg$;:< -~mڅ4Oŋ~6.u. ;!jJgk82I",Et7SEp֘;L(Qz&PO|+/^z~.1l߰9{Ny*Wn3XtT1,9fB)zW4WrW>>k=R.Wb>|t/v |}􃻃&2TX;6׃&ӈt 5 ˃3Ԙo_m[juY^P~=UMCsjCWIG,o/U L9ܨH/^0a3}JQqWNZe^w:I>JVRmi%e,\NzeWi/# QJѓEAқ -< 06 -D9J)jRo`Gvۨ%VOxyNm|nGWߣ&5IȼgYux!BE +b(bF9TeD>QKԺ~plY4_'%IrbIlD-w4Dj/$PZD*7v\2 M?b`,DgoGK'ZE´%0y!WZ{skhW/?:gR;&Ft><2M;cc2vhΊI,g{⠛:H FY6.PHJy)W-*N I3Avn~훊g|̔{YfMvaGdlRCK߲;i4_lCY֐z`^4brxN~~zg19qHMMY |V-Q_1CZkF6tȲ[5e)9&;?.k#@ܿXL9QNæk hZ½R#Bha+A -g3R9˿mNmWR/ ͎= 2?t/~HP,2k> -oݥiuG}|i_us[sOor&"!k$aHs ]O(+Y#P!Uե)[@3s.p -4GߕezC\e--s~ɴN2A?.aȏVdtC,<_ | y:0jJ*G' V8[W>gzӦFG)|[v)Ze^͔K'B[zL| 5%{R!Sam/+Xͺ:ѩZ,m@ ͞\?4*I~A^zxΣ^D'Ҕ_aj[_!..a'Wix%;;?ë._jMUD骹Ũ )K*Ojt ݁1}JԚ{ou|85\;_G4~8o>ucT 8X -M'QfBbeo^/ -p> P #y yGN(xxܿb2iwϧuD\MR*/5ob(I$5Ŋ3dJM鬰>J>>_aBQZOHZfT͈ U2Pu/F6Ts24mC(Đ -%J7&|% ,Q#mZ̷5-N v`ЍYO$)j>΅X|G5ƻ,D吁_Xh 9fP+r>5r-lv_À]t'wz ac8 tz\\SęJWeU &˧lNw#8I;6=*|x:3OQf"Ew| /_)YH=Ի20rPM`pkw]&Ww1-+'r #yhkأ A7 PJfluE<5=oB/WMo9*N1Vۇ2 _?}>IFld<3G;)".|E\_E#į fTzϳQH%> ŵO`n92'$ e؁nhҏ P/5t{.c" :~-ժީ|6,u/{>uY9B\oʂ<|j2Bӑv->sRϝdB%)0-}(cZ55 nsݹ$hJtN2OPIp pTL*w?dĺ/nA<D%BNd%aa HуRTŎ;,ҦDf-il?>E4/Z<q/ -Zv }M#ڨ{ px@]vm 3 nۛXo?Rb'AJ)ᚐdҸ -ebEp[4Czd 6,@f1&VNwo\}i.(0J6 ݊)m較 Edm7$n@wӞD&ƹ[Vj1Fn]:\ kn0W=/~anyńy?>P؛3 ^_[FmSJ?B`  y'z5H'aC6YFe1!4cw\r=lFCaʾDz<9*2Pha3O_I)h\2$fu taD/cs6~3\?aYuvZ{mgҾFijbnDB7Jũ^uĿ\/>#df$5<(>-&ϦXa7um]%7!6B,; -w($K.ab45gšœg)ϔ' Vk鳬:b~gGW X41q1W]Ŝ)M~#|oC^I}tYڭyt+L"$^Vٳ?]i>&gݵ?xP6c7)tR@DhtwE%s[h?IYMR_%! 9(Yhc9ѷ=8,d5sV&>#damu%%4;^bԡ:Q Jەs k( +A@9BxQSn'ُA c 1ˢ/I[Uqm UWtk>Ei=X%C`mdkY <1"t9;]4ŦS߅jڬey=yE:pĪkKc,0B"d;<$~Rl/l.%+(BT ֞euZ0OӇ- Ett!pD*{W z= 6oh+RlĺE\8 -vX2 -hɽ $EDi%luچ.F}k`TN=b")3JJ7RW߽NHoJtEwpvVꮴvsF+_aE Vd>L߯V-ѺO9zxu1Zy ̦D۬Ý2"#DRWE6|I+{۳UHʹ쥏W`4RGc^P 딊05~,(rk㡸}|@q1n4ꘅ jV2TpʢN,h )1\^s+£œ:E>PއY[h'aNEn4}69G؏WMʂݲ1z$b$}ʤ Y&~IYB BD O&ҥ{T^in $MJkttUпѦy`쮳>û!?䈪k^ 5؟bk:4{G -‹&9eegeEXH'f83HUG5Im*[} ʻm/P6>8ǝ#T>=EkTE䕳4¥yQI; kZ0:/]{A&'T#pY_je4:t$6I| z532gi4`Kj=lt˗%O@)?+F]H{B&49zۦweG ֺ%σJg,R}O?rxk  $wN :%mO⥬D3M@r'SEdsv B^oGq0F,bL&%ah4ڠY]@SG XGV6/ t9{e3ޙZ\&I“'zwͥB*bDo`wES<ŕ=%Cu )ubjԼ1Y'8L~hga,J{7@8y -QR m}_GȻ&S2><\٤LSq1LYcF otuܔ̨[FW¾*y!!T82M7qMǪ2@|rpf-/;KQ(9I^[^Ǻ۞ Ѫn AN2 5ڈhpZ~ ػũ- .!&|ix~k#B>z4p`DMPP -Cnn*qjת5V]TxFo`'2b̄ae }U,dڑ#<會t1~udtQXi4YCj;`}"fGuN??(J!MRӆ[HREl^ޢLH{'\S č*˰;xPl3PgBߨojMCh!Wn|оn=k@7uN%S+I.hA &`%㲓 >E&.5}"e TQ+CVd%5ON$kvuK ?Eph6 Ю ")v rC&O(dIAo,]%YSwؔOAnպw.0UnMӻƴnǹX\k:aFZJZQphԦ1Y0B$tq&͐#^3"R õȈv5CN$kb_)0@v/WLhꧯȞ kAZ4wTuS'Oj{D 7>Tww#0CviZ m Ρ\C `)@ޟ51HIeΉ%^[5?hCo+s|̺*$hF6^'2&Ag@XKׂoG s --ñ -t6$`͆JIб>`9$C#2+4(.?وfA~\8 -km!daAi; FT2]\0K mW@.}y -910C$_ngӌ4f d۪w6x exBdˤ_.N~F}V*+{4pICg"`x˜<νE7޳Z P2O,;m;|{tV;4i6|o]6@9IK#ǪFh_1'Z 44#"mWS̮n72Q]#F]ꕩ)wA":ruDgL0wޕZ0&DiZii`RS(KA|}j8 --0(\E7rUM@1x.do >{){S.P`Bdrb]`\ٹo zlz72b0 > \"nH;G2O~ )YáT>8VjVE 9Wl~g -m6yn<TݷHZ[xr^ ۽Hd[jZ 3|?]oM!hCʡ::iOסӇd -Q_;͔ƅO2NhU+V|5KRJs RYK{@8ǑBSS %.޷VsĔ,IxfhY%c0V#+ǘD,{O$o6Tbx&4tfKCu/ÿgtl6q 8}pޅ@qPIg:ߦ~|Ѓ#=G^j|{U '=\4%IX,^?ظ|#V6_/Y3kWoʩ)`N\M0hߐjދKbOWC}]'uӰH]ң[L6X.>n[|҆948\"XEƂ:k#&x/gAk !3:,_$2ee? -[`Ѫ\J'r󢆑1ګ3G<cזK7xtI=tnq?{MNg&I0%b؂8arv[.89'2 - {j}+Y^do97$!_S_鑒WL%߮لB-B=gK3L꡼#h -hLUey?zl&C -BЖMԥuXfҿl)O)MOxa&gF}>`?%kK$gڃxզ:CyXm@A[a=^!- ->=PzE}缨*)YwXcI0ʷ?))&q2ag5g.d" Ώ@]R; (nve*,O$yʹ:ttWۀ+Aߔ\J1k)`G!5K+̃{>MZm?4l*&nv x-:]+&6I(k$t'sf8-u:pڥ嘣3 L¯|l82$KsYT 5 61?KdMAcou/?[6r}h 9>Rc\\ 0dO.>jo)< -D!|7ѡ%ִX|O(7hˡ. D2FT8*hhívZ7|䛛݁PyP+~=GQj99>nû'VJ>M{G6'-?6ɐ֧h;JN8E/ gQ9P N} -^K(!6bE6L(Lm -/ QӜ -3Xnf!.۔8+x`%*ed&{"V!N値/5MaMȧS巶3-:=)-cv_#夑f:4UjEˍȫg:w G[싉'oT"Qjg,it[Saѷ)p8aJUu 9$-Y;Ƣ3Ĉx=ߍX;W֞NnK#\ey'ڎ20O/ߧ_K~=K!mL,m>閿K*&6#IaWGxya . yJϐ|dO|Y+v|x$7$O^U?&_lԊ_ԭǴz"Ttɢ`U"j"TYbݭqfInQ!n. )re?` v_fzlM r%ט+g[B#/]p||U3Iy`q{jS\ Y~V$.|*le>B35NS3!?si^:'#Џ4.(:v14+?E# !+.}aSGF5dL(rSʄiDalYA Ed;G%Ѱ^f6 - m}7n.eN>&OZZ8Xu@eQҸNfVwOF*ҭ$ -Ԗ=CKC 9mBuы+'=0΀$(qUPMIy⚠{ sF3=GxYαWv[2HQ> ԗE_/Uo{䡇Q on&&h V_hTd}y|Qa["NX^9bMCl'FZ xCLn2)k{3Upp9Cy1?ܕCTU -l2bVu\C0oBD ٤ŨNZkDGɄ xڱcmS0V -Ղ7raj -^iݞޚNOЖיOl^gm-I:%}{bKZ(~}DykwȻAzlNPs}|>ůZt=Q-ia&t>,DBqL8. FV(lY _Q9e;H"7y<4j'njQ"͞>)|Mtvh?}˂֕\qܿ,UՐC[e2 Ass -|9\{ol=XCKFBo3VL]վq<]_ ob+r^ܐjRSuskB*.=f9o ͫvMݰ^hC3n/V(r,{;.O&U2Pz#{`\\g!Աׇ.Q[aK](2w+0CM&Y]3m}ފʭI2x_n{|sEʀq dN(@rz2ꏲ 9fi읒S3,HTFvSb?+1[Y\_eJMJE4@)2`@ ZpSZvdOCL'dp e_%_t*lqg@hLOqxWڰ}q=V -dڗ_gK*͎HoA&O -y~ bW+ޏSyҰXaJ 2nn"9]\(& Nbߋ,BKzf,!f9UQ {b2ӯ$.\_S'eks);z09V{M$k0co]EI''"%s+C7>Xd!Fa !x6'2Ø(D`;_}P.ww6oB8HpV I(zLfkkl@}pNcb#1#,j77;D݅@bKzBFaHm_ܡa+?/hf୮/l/{gȵщܛE*pW(p P9 @aÚB2(zPeBUz R۔=$bE ĮjEpo]Q8$v Vۛ3+{2eş;:,kU|ҍuv5Z;)96m(u|Р4*w=uX߸ɋk43Zo$E _" x]8)c\? )ƝQAmv8;r!|!tK\ @Uwnf K8'ԝ$Ax!=D,0߂wgn|_xBӲݕ8SjP9 -$ЀIY_'>kث N&) ð(W&{I_6b|<=M+*3Nyf>-{%,0"W8}̳pI XBt$zDW~-Q4*袡7,YQ$^y}#snަ-a2)u!52~wGHqf'8\Rw.H/ s u7hvW'JLC 6S`ƽ;+k`͑E_vn*\BJSY/VvtLν CY(zZ2e\HaN }Pdە"E)滕#cN$qWoI(B87"Ńa͇ gMj.~˄Z{n#mxꃀC1wу4^r<"AUz@6LuYZ6Q E hmҥ=@z)x&eb5tss$ - u TDsbGz8|)1j 'mxn%`rҟH!:I.A;QBwA mxT:hWƽA&hU$5hÍYݕ֤J]V>і-cR ?ஓXԸ_[ˮE_.1ѧ׃M%l/Ol%'Ē'ApVYc.ڍ |Q$o;gv`= j_ul;lz %2.HZГ*Do[Q0ݼvT'$]Q@RC2LɯQ8?_uIϷ)JNߔ '{F"C -= Lf^N]02zgl2-Q&!c@ -$zmmFiw?LZsD}qKOl!PօGWi!KlLEkM$A^*תW= 1kHjuZa?:u;9mi&v:kbN&O4l6i}D,ZLHLzr{rYg۪FNW1b~H2GT9?BoP5;d(mtYZl#LNH$qܬ;IyA-1uC B0mW8l8a?D;~?i.[wu(C c8>9Y~-QzUdPNsYT9IS \ ᚝1SG-$dc(k(Oa+I\s%yX ~u^^d4 ^}WLUinAL/#~S+.10l''h}a<)jeq9Q߻([x+Z\'JYIy/Kg")9oXg+>|xN!|k٦s Jg5 -ٕI6AP劜1)ZlEF/Cի؈OhRѶl/6^T 6GJ!r2앯}\΋Ie::ore2Y9 E: \01KG:0ƂφEGKkg}E+$Zv2/M>vyc 1â`Ya9`;/ ~iYG( -SI.:~ٺt/G`d雂Tى |`[-Qv1 KHA i:pVnC b]%UB -j;xj#zH]M_]tm&v.5;$5H)Ajи Ҭ $TmK!N2aL --dlڠ™)U~w2֥dLa6ETbVN?5ﮮnKc};ʬ\rb t1v2{'xl=uKVZQ_B;EƳU]R3&ݠ[q6LU3k@ک!IMI;8Y~~{rIB =A]dP썰a%T{>t.A?\c#ֲHvziVydkts![o?[!X﫴62=B7^V͐t(mo8Rxhxm9"4m/|}&ЮN ^E+4QT *_ |;d#tܝGG=jѾ9{Y%JL bDyA8?'D Xܭ ҧML0N\]-?XV6$inyٷ& Qx7QWH]n;ؖIY4ϭwYXDH=bXoZaZ)(iiR$'k {Ms+}lG\#=[py3}߸[P'݅['AVGE`os`kE`);r]{0'%3砰 B8l XI}GT ǦaQO>FEzzp#3^<5,Y'B$6opN>;xk3I"dPT<oӯz.$^_ QZlF2f!zV#2)}+ FGKv,z7k(kMEv;gv&›~g^ Zef=́"v2FdK-)B>q~4 I%QSOgdQ)3jww.JŅXB -5ƣ-i'U@!> wY`r}5.@GU)|Lʩw2ISuVU3 4^Ά~M[33ϥNu[F3vW&֚ca@e'MQ , -ˏ}yG$S:FVjV4[I7,h € -a2}0\X42`%Т8)h -7(sH}T2L`)ܹ -Yz~$mu]X9p%% h5\ 3J?c֗xST^a"tVIeu5])0ɋrhnd꡶V5PaJсnPϿ\Nle, /%йsa :!2)ø2fL2LȎ(*N(Lrwk+d43:)i}b.NrG:ovoU9 -1lX(HuuD*:{.OdbYTS".M{?3YQUN p@=#Kj0O NwZ';.a_RٽK dY`]vG,TSWP7Xr -endstream -endobj -328 0 obj -<< -/Length 28785 -/Length1 2608 -/Length2 27247 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴuTܺ=LJwH i6twKJJwtw(%tJ9 'Yk1"WVc5 FV&>feaTZڙ8ؘXX8ĝ&.` bP2sB"XXx i0(]L=Z2م,A@:H8w vFߕ~g1dMlζ9@I v`hebg[ԁ 5IU5{U% e5:&Ha5WpWSxUT55T -> -:ʒ̿`/nf? N`h\\ݙ,]]NLvSvlO'a\A9]*{Sf@3w_N{$ Bip3mLɕWV؛X\ $` 4A @wu9RCVok;fruK\l@7{{f Ǧ (#%(<D%!`cc@Td.vF-5D''k[ sʛ:0k]2 1!Y],#af? 0sZ[!&n@+o"dVn d!2 0_fusP  -.W/)W;;E{ Kq&v_Z\iN&vv+[Y2.&Y!{Ii-}sYH3[ QB-.-?a 350qr2Df''2@@R. 06 q  `7f0KAlf?RE0+Aҿ /8Y[Y;' B17v_ o6,AV3OLӟ&vC;@m -$`W !AO?!k`Y!!+Ð DC/?!A/˿ZBC -R ,R-?V"g#8 8عw+RODg;g usBX9sw_ F R !!lAHWϿ D:?!Nb🗉g?d\@-ksX(8Y{@xVG?_bb`oFTY.H5s~k@ yilb1O`_H[6n)}@"w-P09 ,/g*֦ -{^kI2W6S#FɤP@6'_c:5P.m"~yj{,k"&Qdn\Iq~7W=]>kv{6W~X@d(V=!.2MS]img:i,yĻL-zYA/5Rt]qC`ac}r{C[vIoXժ]F#*9]Ya6>\/D9բ$.Ӭzь 5ßhgj.#3! "qr0i Rhυ@mz؆Nġncrs{4v\H?ruVqMނxmABt38;7Zd(_,ie~Z*'߿j#'x9|| |; Nx>Z7$دCJ[96[~rG={q@&p9z/VR>Q(<"m -Be˛B?BÏפ8 -7qGfHt j[ШUy٘6>M N(wO?K+sۦU|}o Ol$_켆g-gr+)If/%>~Ø(VS%=N)#4Ij;/]:}T%. "a!0h}%u@thH0sf$E7 -_%9 K_蘝!&-12 -SQ:}{M/N} ՞Ǭm~PL>&ZzbՊd ~4˭t㎁pwX=[Vj3`xJp8 -qاOaJ}u7&^l -lN,,׌/kq ](Y|Q=q'tg+߽:{HWdup$ng䒀cJ%}m?S4em( -em bn!i-kb(/>Bgcڋ(gLO/l0+$/ncMp|m|?9 .;e_ls '`!w CW<*]ԢbV+8SW-8$oHgd35=eZ8ު|=3yrs4F V3r>ЗR:@axf@y__mF -[, !s\i1Z?h*IIthnAըa9.1{x= Հ I.UWMTKLixwxI`2nu:֘ V -1k[mvU_U~+;?lGl! ̛8[*C/Jic|E_^ D.OS O1 |3 n|kpK2(MQlQ'p#b6Jn W =x.63CYt-}t0\ Ou̎/q؄>Xzb=e8]l՟ ~$C_0T8fT@/ NS>e.QKabJQcG6j$(Vfm=OëMuR"D8`2"LI L"^Y=FqʬXѐ'} OdϡKw4(TN~! |6s Qd6|Af1 N ذ/ C$:'0NO s^==Qgj:=/ZEHD&1-{5~$z\v p&uN0E*C$%-Y\i8HT採Gt/phc*'Z84JX(㞁}߰??P|8kn631J/sa.e.+M7 %6Qm1;=hU~&P;A'NqhmHzxTLb)o66^Ze[L=(m,,ش߫@)y H崶y?mu&2>nBMSw%hciP2Y+ 'jů"^+Q4lIEd~b|pn:=cP \05G΃u%ӷ5carpX4$ç}*R~6vt+>cgR.hoidaC{$nQ1ې{qu캝 GK`Չ4žO@^6 -h*|zqLZRnL.;̹!aӡau71'frBRh>B 3 6ya'"$qh%پU}&mpyE l$yWLyIL3=e?պRE.[Kd=It ?W6*r>g{ufl zqzD(!\_P-S3kcꦼ|0Nޯ4盄iQ {T?N6R\[AcgSx̄k*뜮*"_’ec)ӪYE*FC2\&_k+"!O_{|7vTaч\m/ҦdW~m,#!."iΜw:r J{m[΂39%6*S^TQqcDiBxVs&gv@dH}S%UONs[IKB|p=G%I&F8h_'ɘ-CB,h;Mr%Jz:a Uv$(apuc}O?L -p$n{͏c_(5#$]i򈮳 { jk<C)6@2A8ި LSV߇fE(r!Otc9fs@+lӁYi%0IKL7>ȼV~.EAY@I\u1q9*!8uu=C[-j;qbHCWV (=H͉"kL%9oTXvAK 0"`87OFaIe獟aPu2y"6:7/XH"">*"xi< ӛmuK`>U$PJ=Ffϼ"TTrx.[̐H4ש]/6\H |H1FCddUT(bp1ՅeUr.9|eqU{iѾVƨt!mʯK#kvr%ħ/m|k_RKg}%+)ڃmx1'/x$N(hR\GN ФqXΓ?_[~&aqst#R.Bs.Y%bRhIxr&DW^4m(N!8C)#&Pfc -.f7?bKbw*?)[<9o]!Sy;Cʄ"\sƒx}zzAB<%vg[8*~\m& ڈl-` qx_u-i6q;OVso+!Μ2={%7i/H~v"wo&w2 (8d5&F3iVܚj?CMI~g2kZߠ -T,.2ChR{I͔n :6R7Ɨ "+hQ{.;\O9pT;SD}!Oٸs5"9&ޯ$? -茨/P~s!Sfڣ45`*lr;vcѰ^smD B=/?=#a%a,0:jk6fƩEF!VK%bG&Og| 3)p^uưg[S #ncJ3*^'66v~>K |1OoC,$Q5ߜ;Ix9=F[44lwE0e ms6raB$k3>$ufz4T -xT^Kq5Ut˱^G`<(霮TK,*ŖFי~r܂ě'bo}Z[O?>k4*d EHqf̤`[Ř-KF|m纟u:ah|vT4`%ILF~Tis8بEGr54ecZ /ps;ɇg[ ca'W=F٪r0 ~j|0haezpG(Bo!.زԱ7ʶI ȈLi-QaX`xQ=-d*w6Vͷ.(fu(LuAAWZx5=iȅ0N\4c_o"v$W(S oU -_I7Gi -xMkp@CPw5~5[] T.|gl)m%ٓqK[YpԱH%|CX5僮"K-vT aC{kHrS]wB`NšԘ+tNcxH4- 2۝6s*-F/jFqm5F2Yn81?R^$꣸@:+#Ij'6ː67.a秼RN*sx*KQ¯P|%l69@-{2~>{ڋi ELygJ':%,~kw8_?%e+=MCg~s C 2О{Z8Դk^!"EIX.VoN>UXmvGx'%87xͫ#~3NQsKWt9U,c|T>Nۇ9ԂE8ɉXAKzKp{޺Og|&}pMUb׈RG5Sp3?7gkhܴ&'JbX>G{liE* -lSibO}B= -|O -6ɽRsՏQ -v'zQvaEѽԔ_ЂKQJw/ T.5-=%*|>}$`j?0}f_0zbP4('o{\/Ewيj鴢;_8@q2N 0\ns :EZy6rԱͻJ17o4eLm?}<־KNQqOmi:uj 5Hj] eE ѸӪ@;3G1sQ{cbgw+j[wBw9n~Y[o|Gx`QvdfE ->PdKl^ :ຒ}>fhb2礏2)9):x.y[8 UY8-cjVXKAp^h1LMH!=G=mE&uk{x*;dpNZS"GlV<鑒sj&zʾL"(6ֶnf;n=kYZӅuXƔY~OGߝ2e&pXxc\tpz6:N&saeJCKLHr)BJ !/";jN%mbYR%_ k1U8Wsliψz&XVHqY0ւދMniiz/tnxX+o*<I5,4pCp#f\:|u]'l&}WSw@flnߚ+ kp,,5 ~`^maAy͡cof7q+!39 -Xz.6mW$VNj=  -}F;? nh ݱ2LRMȽ6o2NB8jz\Pun)TQ_l@y,sw?ad.˦J+'˽nj=.u\{<*=]Ҭͨ{tځEhT7i  -'i݌(Wn V<}&iu\+pRzf$q)⩈+ԜVŹR^@C,&(Po@AKq}48?b'Y-x_]bИvlM)r6 ץo` FX9Eu YŠ'f SIEb1(S"{B{hmnɁP,eG]NEwDo#՗W iN/Zmm2 <΅2|Ql#C=$A?Ȧ C5+0̪\ {-)~V6p1''\ BpB>CnF'c#$5csjB~E]LA"|s%7_)-=8/V`XOB~E˄?sIv؈ɞq6PlTiJm!*Z?>]~Jz&J=D#`gxi<:7(A@($zgy{}Jo X*N*rnl ]/a0JJ7YN6}65XX>2qWkoK ^7|S" +.%7XvpGD*IsS3 OF(F[_kglLYI(>n 0 qs -PC7#4¢2n.sٶHh6f5C:$S4JJ:/]|N!Rh7AzE*msM<^6}U _>C4nz1F-mKߨKĠ_a߻ -`Ɂ#T}(`{[e,--i( __ pGas"Pkxb0Gyw`z#1~euj@O" Kv nyXKzn.W_R3~ܘ+&WPp;U^ e}Qk#V9p< F9=H濅?'ւi2}s:'@$DzM\e5Zoo2sH'}Ec~pI5OV5ܕy<625}nh{?:>Pkgf{q .r!4VyƍLxm"VWV*.WOw|, 5fLZϩ\i:Hmk-h93v{R;):wN{Ynު{S!uH]A3̐ =;䂝QI[jr=pPTqE%U-eGF= KHbiu,XTL//Qi.P[<6Ype"FJ{+?F9ңy)l Vה||x1otCkPM/T㷋V 빹<҃t/ .)0 I{3 -Z+ 3*ý"\7$s>*8_ -u4?,P^xvR@r#O(\k%r+3M*<.[ad";r|7u4;ony9Cp/yVTUx% !Gf1 4""h]4î~w}4e-yݘc!ʡ3=)ڤ$ C0}2ca>^AoG/L}W֍:pyRGa\Yoz/$<=*O̾8T<G i"p+"^3ċr3͢~:`HoAJ֣\8K4n]t|n#9zkŃaįҟt?|B AIcr(i/ un-!!!P}usx8 o!35m, ~Z{{(/~V?J5=Qv6]Ze~L M!"xljB2}2VzkԞ5Q$8ݮgU];ڕw:>o9{"܎(r~7(JbIܰꝓ]YYR8;n4P<{AZO!8%EyHIw36R ,`*Z+UB ,.^ -śZw4 ŠfsYhۯ[{_/0uPE|L7 Ge?E}gj,'ZIí}Ra'Y2n4mߍ~Oi8i.2Ko2Hi7g* TZOk:x1WI9azi,δƔV %;S~Vh=xծ/Ä=DדdU~ޖ?O|:87) Ee,*qwi"L=LƓd"Yx1 YwohHK.p,B tW+)ij2X0S -Ik^>b߾q]z4+iEi{ro[X*h@QMRpu"ۃ UPkFHf U!z!a2ᕏܞ~?W9Ei,[g"{"[E/RSӥt:l3Ou6ŀ4\htf^yegWspCy=m}/@&Ыe4o8|elƇ*MM Kխ/ML]☲'"&R]O6)'҈ax\N,a{@AJԚ 8GѢcZ%?Y(Ŷ%O}j{8|OW6Ux}%g8+sM{ eGbY,u[+ZJ+kM8&l!vĄnzDbwVK4S45T疶?nϡɁLM7{+4EdQ+3Ƴ$acR |[#˺@u~W{SHÉq}!:W2z_D`S:74}=$>YKp8ɭZgK[H w]&a} -MRuI0Q8K"3xk{= X͂ x/ZYֺ8^ϋYjI -&cXͬyɋ'U=;TL.F9%6TjȦ_<NךO*msFgz}HoyKXiE>|;ֵ̇ {$B2Qg,We\z[>!*o}^ axus=x+bٮ] ]$`*Zh!a H HP!2On v4Ye[z_PƇ[eqla 7fr[(0 qh&PX \Zr788a1) En3-24^|?I᚟عoQ:͙kN$(S L;gVU\R|0pGHX+<,~||S%!gj -a/+=txqp,ɉʂ;ѓZДbL@|Gql69DV W96my?9ghꎌ$# -+LO%.i_0L5uYrsx};N~ 6H+cڽ65f@ZȠ+"I о'.Nƾu,&Vc`h~(YI&.d&@zu,("CƽfIDY(?<ؐ+ʼ5 -HSfy9HȭhfGVvs_7kTiOceh*^.ƦvЃCnYFN-[ZβԘ{3IM>M7s`M{sD73-Xm؎tqJ Xl$XR;$y6\=o.?-X9\SrulÿJ3svgW lW@O_gƴwR鈇F4UM ޯBO8|SݎOWx&伯:6ey+!,"Jad{{~# aD0 yv xm Gi@ :og<Om›fMՊft Jݥ螓?p?d:4w/&"? لnXNY!mܑtkعys )aB-l:7mz0&umvO|`)d7;^dqr#_]f7 -S`P~j=4\}^,^ 9xp=8qVUh -fe_b=6fN=W-X$fr1xYwcW,2`4MĥTy!AMƸjRS3Zv~m#Ԍ$v%iVOny+.O-!јI"8/ل@F]ʬRK#-DГBƀ,_b\T_] *XI0;we25Պf}3\-+njT(:5m5R'RZ Uks@ӎ_aÏttqῌ |ɧQ3MWo15KTZ#lkm `>Q @_>1ч. tZ֩&#ݮ| Q]6~V+%?'Vi MyBC!$qZ![6 5!(ӛOrnZYs~̼CPrb -RM7Jun4RF0AV/ϩx,3gUg, 2{hЇgÀf[+κ[$&NCvMH]=p'JAemٍ~}'.!Y=s.A*k@)3\@[7ikYV1¤jE -) _.8֎(%ȩR|CEb?..u"-}yAci '*x~s 390;qsVWL%-%1ܠK)髂iR| ,dzӨ+-rkfg(" ~Ō 5^o -"u;vU{7y#\PڕO=s.S} tL]uܶU ъ+gI-9Þ4Ouޠ'}p7=HO %8 (GM')=~ @?ZE$u+#`lT7*h:<4,+9(o#K"6Y^P|v%u) "c6ǾF}uTT1xB?73V}bufJPm98`u.8o/q.y{8̹~eoթO$/H!mY (W2Kt- i"h"o:^F^TFXcl K;OsIſ~FNx=\.b ~'aElcyknz>X&tE^Hj#eJ s!h;cr08=) s02 CDr&QKQr\̈́kўDJhcpӗYk+9PI~%F4hHΦ*}M-8pyʻZZ0&[?uoZV7de3\{D CDT%D 4/c[-$v[G^I6QE-&~k(;.(^´+Qn,UN؁mS/|Il5ecn\uzΫ$JEtBz9M8^jw);te薠k:*y&\PvBg8%|67M\7o֜& =W)>sxkO[\lV}tvFgr"گ)(Qp+T,@wuM)IXa.PNT& J$s zhfW=Jr[ B45?PְzZ,J04Zz>4!4I<#F;Nɦ;_^6'@ٹiq`[Ee>&W]aUU\jYJ붙+Fª!O =Ä-bWlg$&PFҊ-WA˦s'˛sFp:o+"B6ȇp,̎t ٬ZQp9A~{{Ƃ&3@3e慧wh}al'RԄƄBCNHBE;!Q:d{:iFUqv k"";~dRJ^g[d4?K8N%}Brf1V"%.'%!qlhVx֕K^ љ7rФU~^9ggl:lw 9c熌v'2" c3f׈&qv㉃4(ix #>"#@\ G s a/:=~v RsDTS$ΰ 6sGE= paEd,̳7[칈~@T ^fB -Z#2-?}0ue-҅IC]DD"iL:\i1G܎!/M -aydQ [-@59﹧<\/"IīaM -*| K>Ǽٵn>amGYv^&0a:yXn;`a6 s)È壏55RҼږPN vP:'={(v6J(VaFUʼa(xzu孔JY Ӛ4c Qua poNҼ駸] \8p@$,_&EB]W9%_VR+DB6lػ ӍWw$Dp(/f(Rnu6.V-0rPyg&սik ->sbic+@VA|`LŠM/ ` 嘎Jt R 0&Cf|xHIqXBejc.p!tOnv*MD_N⪘KIWceN PF_cD(,LO%OX6z7*Za)" $P' _:*8*9bW: M:*.p˹oI';,PU%UAT}C}@ȅQI#@S3Y/`/9v;gƬi9@ԍGg!}\FusokHIkwU/L%^3b3UȚR\`M'5cxҹ;㝗nDN@{$%ӯN{r{o'>n> : |jXzxjpZDfKk()olEZV_qLr]MHFVQ$B\#ʧ'w]mr{>^L\ަ@oms(avgHjwrYMmO|rY(5EPcf;p[%`8HMKHZ06Oʏ_] w.1bonBCwQbGF0y Ae}'.= Nnx[,hpYL9TLX$AGpQ77G/ RW ANBYgLu(Bq|AKp 0;gm -i 7yJ1U9]pH/3( ʴC?.X2^˼dQp]XaXG~{Y,N f/~BtȖ Rr; ">2 CJP~ypzeI](ҟҥӡ6x -_ihA%P,|6o;LGlĴ/g嵣+FigFԑ4 [H{} M`CnLΚu~@?_+_FCrU4CkQ#@Px08ͼ J Vh$¹%ݶ 4>b,S Κ(a@ʘ&FAy:% 5,rb$?N^wDt/+U9>ZSNӐ)LnGv8lPGGɟ 0ɽQ/TRӡ{- DR: Dj_L1β`ҳz!UN'o#[A2 n-AԌ{ʈ6[ l -.ljk[cbxH|vDҳ]H(_tEo#b~xVW$߾toa(SECPMmcz}DzK""$8" - mOj8Œܻ+[:}pPJ?0`]'T Ё5!⸰/"E:R3r gZapr ܧ}KyX>z8[iqd6:ʕ깥?(!)ԞTH"( c4>Ux@ Ń0yk^+>WJw]J%pߩR\063rl厀de42*~HOB4_W>oϺp!bi~5 ޗ6`"2ϒ/֑%(ۏX!,vʝC&+f҆U_56$T'+c:Fqr/c9$'@eB}(+Q :jƝ~+0WZ@mew>iʭg( ^%85n&/uF&wV: y,1z- qeNztl"szc?~0&Y/r'Kc1<" ף W<ߚ9,PZ!R8ŤϞ!wb鏝7 Oժ*+DWDtmT9>ޖFyHuexh` a29ػ4B<Åw0DqCRq vJۻlټn%# %Uj~,9Ʒ6gb\pO2Ԋ˷D}4 :s -FBJz>:Ϊ3k_35 @Hcw3/\o32o".ъ>hJ܆ήABk6q{Q&tZ$8A3RW%]@ݰ_\輂<(BV-jFY!9֞M[dls'b6&͚.&񄢵ׅ5R+ ;w*dFϷ1'Bj :&aV@EutI)#YrT?'2CJr=H.HxUx*E'eZ=fm3L-ވcjCaל:!(0:eG]%|оn=k*)hb^"s6sge) WLa+qy#z 5`HήwWYjz,C]*]VMY?ߚ-s570J 3`ݲ1}< {N8beD]uu@2>EKI+hHay [8dPh>I7f<upҚ f!/^q{PI:ekYtd0)V{܁5{T+Ӛu$!a-rUSkNn54mcK&&Ќ%A?kע'+/M h-3{/FCgO3܏Aŧ n\"cuh~>t4+FMHwl]ƶlI%5χ7%) y)E(p8wj[7{\5 SqxckST%"?i#85t<6)ys'~x1}0o[|vKYД=I1Ԏe`Bʮ?*LYEv yxg`X= -ddžƟ!zȞVP[IyM甆 -jśb\D!wF֩WѸi/)0Mu‘*6D9*UC5*y?EYq4 ifGZVX&_ A]u' hkk] -Ş?OwM(Ւ(|wFs䚉oH,!/1U/ (eCJ|q;_z@;'fbEb.l_V_h3wxJ2pAQU '/qhBX&b2s;oMEnbܬ 3 J2P[7dG<~D :UO(MP,Wc|ma^W0MGϚ:<~7rf^"U -c.+ 7iՁ91^_-<@C5KѽMt>a.5W2d1nfkhw!Q5yP2z -0PbO$ >cq*:%E?Բk\a7AWf4k 1>L^ExTQ -}g -U\B -.z m5#aoIզZT=(f6yoh ·tgoDb1wQM^.EK9WuG"t8zeJ0PHfYLJɋKl[wf,膌_5!b\IUNst,*y"BPzvDnETق7  } koA/UxsGJ$cT^868dwR~Q_=F/ܫCYű -Hs/;u[VZ aQ[X [Z9yz ʍEع!@Évw$ `BrRgծX[-R Ln7uu#6 x {[>?|!Kd3o]!G(wz.z؁QVkM-a(ZcGL-k0nBMAf J@y)a^~QX8ٯe̷a.IOrVZMM=~c  :EDևeՉ|Ɣf,@$ցW/yW 2hWZa%`TkӴA -J NUٝQF,Xʤov[IiM|,r v'#mi?y|8tJ W*jD զcMϕ뀇1}Sf?LT f؄ 7sU@ؑ<q]glN~bQ"w>o4Ts7rIAx؜{'qY)bEߒĬ1{pM F.e{3#7T7YAV9Q1hXAMxZܯQi -Ca^+!\\nz8k[@!!88X]d8L/R8WdYC7f2}2L{&cYٲm*2Xؙ  - $]`DByuWNX&}b5P>"Xйv~Dojſe+R{<i Ex r2c(EVtX &]:?H72>vSq"jq;&TQ2C*|9dq=v{K2t$CHW"b f\$EܸRN v6Q -ݤ\+Jv0>@ {TZt6^4sU*ÍLxxj6 Zb#͎:Xrm5mymJBR&O/t5 uT -g3n1hh >?p>wPߦta32EyUTS{&/"`T^ӬϪ"(PB{є0 p2"d{˛H^U!9zԴ|S8lg7&]S1|@橯|I}?]8 wpJ@yxrﵱb񹷅Ey ⋣?,yD0״A2SDgV,n -N=C˜յ/e["oJ)&ƨ7aoB -9J_TFMamX5q>8brBu:LeO>7Ɵmʒȓ`AlD-B^N/̝H2,=iGܤ$޷澄Eb0$)41aj%8:IݐM<Jz2#Lg뙹)/ɍT攝/7瘛yA -9}:qb*Uvf YMb݁/&٘[ |ڱ.gϘ-=s,s#-2llbZD{!_#ΛU `'!#kF;iΨ4Ea!ZeXׯ]1_:I"8~Si)KO_nRL)Y1&kK$92CMvmWvʷӍxXӐvD˥ү#xκ"ٚzjCp@waB -HTǫ -x *gm<ѯ™٧Jh 8ya "r(-Ʀ/K -?" D%<4tDCW9DpJj%u}-"hfQ=fH𬔲t}G2Ԙ2A=;( tf Uwxl<?vф>hSR?ӷiT!]X- h;.OZ -42lJKTDX^ :zH[ OVxĵyMjz^56M4lہO\UuOVTG); |ccX{^u4gY6S65bFC%mH -ಯ> -stream -xڵTyTƲ",WّlDd " !sF0(!R{^U -ԊVEeܗjaEqmA;Ap޿dfowu B WHI Hn ā'O qh1Ec >L*-cP <_$)Y@LBє`䆤 J Y4He^\.[pLF4iH$<E鐑 R`*Jp.ĂИqѱnAΒ9naV ܶ"5W@N(!^ÞAmqGr%b1> -H$DTL uIAF9Ars\ǑD E&g2@`:T>H,d͈l5rL9'[e@Zc#8!cјpH9|'wՏO"Yr?b\?6WVTu06("@]G. !2!M0~Na @*ΐ]8{_P9ȇt(K#FzD hgZLjIxzOMcY7"0SRZ9EsSyZY9G#ה3w~TL pt"F!Dvy|T} -%gr5\q| un5GAx܊Wٕ=g-5 ^-ԥaM8,lZ^fu3׽o~SӉ^o&3ckQa?~|ט!Doʯcg=1d݁p7N\v&⃋A+i|h1I~ޔ?4}ֳ֛H tgՎK;]+߻m{S݆ۖY yEޜ~hp`/N7ࡸӱ;gn9GazMfEe{LD܍I%)GX/~Vp09pFՒGa5.{riLͩ,knH;K_MX^Yq٪ k -/=yRNzoSP ^ko#V/VX!,|UF晜}e}r%]e{Y+weUGJ\]҅u}KnK}|nBõͯJ}t]t4k -m?Upcnosn~c׼ٴbvΕ5wG]^bcazFтԴ6`ݍƾ_ h#<^`U#qbBFuQr#u{:RQIU@85qk߲Zl -k~YYs=O֬&HoY9'ylEC 6L -PtW}@x8clu7ӟ(:)ƔQWLV׷@şN/BJ -endstream -endobj -330 0 obj -<< -/Length 16853 -/Length1 1903 -/Length2 15635 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴst>acNlmۙ8ضm[mAF 4vҰI~s~~kھ=kP(2L {Ff3 3 - ̎@A!4vًy\.ESww ffn -$4x.j@_@ `b[Xi]DANV.b10[ cljrwۛd - wd0Zۚ@5@]U\E JXZDU%b -j=@R]Uϫ~ zڻOw?jjJ,L`(+wWs'_ Ԗ..1sX$7ٽW}bV}r9y2^؃O\]b~!Gft0%ӟt变  3l8}o02uy' ]I}K@39pU0RoK,BVj謜%>[*TLF,f,&!;h7w{bۿv,7`r|7 `r|?_'&kɘ3^aU' Po&.NV.%r[D ``{?3 ;˟?n˿C*4EX]XUVASp3Vc h$BfvK)K@rR<~eZ!)5_o͔vŅ'5ՃWzIid;;Ǣ}3ohizU,mN+x=+=.o1ƃ« FEaX20}cJWJ8 ry^)Ni¯N>%ـ!ڌq:>Đc+|9+E0=R:aٌ{y21WQN$I#S3ܮ-co /> - J@T*["HP^6TWO+ѕ6ڧPU~Ƃ_ iZKotc@^TEm\.v-^X\#n%YģhWyU3C\jVGӹ>8t};1ٌ*Ҏvp/A}J[YCyd(K[í9\'+<vE{.I&gz넲~Q*=\_6KkQQس|f{KxX~+GkV .=#K]n`+FjY/ }4lYA=∑UևN$i Q/"];Lr -eʆiu^ʹi_ctqs*)a,GP@Ig\945;+P/a=Ciٮ3*wH.S I՘@Du=S}~הu} w G_pY]бeXڕzBi 3 wY[cßjzv,R8X6 'p=C% (/s[Ag@XөO%9|/`2W==KG3;H|v;W6ft㴇;ƲI*!^+GB7~|e>^DCVRK_b\KO5*VL p'sW^3*/و J1{$̒{csc˚'nk4 BV~1ԉJlqgQ7g@yx87!zkp& Sq}p)׮,/֟ΝGhҭ$UۈJ9 .^M}z=Ac0,w\Az *z<ʣmհ䶢C@Й\$dma+&~D/3X &U#+ER勉ßA/X0\)EU[yzI׋٬omM5ta#7y -Lz"dXFP41 fX?Iu>*25F ?K\ )* l Cf%RaZx|6U(LS?⸐9"QÀM e\&z̈́Kݴy$`]SGvP5=n/v7%A)R| =$  R @o~قV&$ؔwd|OtCx.9"b&'bqaKH8+[AM\#5;EMIPqfC4ZZצwoU+J]Pr*A!nXC]9aor^cOR脼gJ[R @$$+,k/6qx5 -[=lxtRY?zRakVF_H-ewU H-i3֕e^fϕCavlwarȈJ3|0_=(QFBKX;4-hC,⓪ŊFbyh|GnEaQ,|3O6Ӂb@Ġj)O:glgExHqFB~wּ=p̜\8-Ǣ|>[{-UDE~=ۚQ;I?Wg3ۈ{ePeih|iA6~]*rb%cЉ"ƥasl0nrL:Gѫx-b㨖'wؘ,ƅǹhU6Q%f;lX9'8U_1ND?٬d51 -7Wt`dy"INR?:X V+gwRoҶ~YxnJb_45RН\Ғ@&MBc⎢]o8ƍGi}xvn{c:) ?{pBPߕ}ꇔe+L}GP>qR c[e,u\< -$HdEdfSHOK \ Q pOyg4 tIE*W$U?^κ]I^*ndrex:U#eaZI+ 1+{:o:-s8KjP`∆҆F|jЯJ$E53L='D&]2O\@CWSb,eK9(nSdyMW?o2:Kž~$Unֱ~ZtVנltэuaŶbŧ1+uxdU9rXJԘ9dua{?ɎލX2ࠧƢ/|Zp~S7 89~P%NuY)vw=rh k"^D3Lj,d7)~Y4TM=nH,#e.8Aqꊊ[ I'|jаs2X-pdWyyx"}v ڧLcOD0[tN,EBۃ ZhF_}unQ Qk\X⳥sh诖<uG0b3`UDJ-\,Gӡf; Ppq˅}Med SBP_F‹&}{27@A3d=r!5{0.9g*`cKBX{)L`*L<1r]ՅC #ۛk痁/=~!d~svrV\!\?1 XtȾccA-.Ͳ>/#nuo:sQg>I[>f qF5%jSU& Ra;c3h|ID]Հ" {vYfd/F\;zf~;ϕd;?PICKۏG -.UN?q>ɳ\_(=Jf+pnT;zkuRƼ[,(zvY8b\HF^qCG- | G4a*Y" ,F3)U :aϖ)xg03Sl8Q=+`$K(OMU Nx2 D""Lo1˰., h/"~7A0Wba&;CF%Q6d9zvI)cgc|O(Hhb._X~9LמMlnWʘ?gDe2T JǨEO z y8c "^ uڕXiq#U'&~55(bN/ɈHqa`driw `2mIu]C34lfiWJ#S`ر6{FH잜 ,}-Z&yln=bGҕzZn( oƦx 58@neg 䛱c|¥ B?XEnv#G0 /YcpqI"Z&mB.g $E .-2x -Y!~ xUu_ wOca'ƥ|aCnj:۸ZХwX BΆf5d٤kj(>zK6~[ ESKbm:0 -q:F$i'hdo1BBc1MLyn>I!^JEna.ӟ22 cN`,SFX ?AK.sui{޶4t=.Y - Mj0ٝ@"|+;Cl!hh=:rl[!} -apa@T)cahFb:Sؖmtm:k@+0CuXə#5Xx/Ô :!oz,vDāCeڭU K~a,"_&t/#W=<[9t8;`iu5j?_ΜD] :E(nI&d:ܰ>TÀy zZ#-e -l$ ej#B+6:g o+Ql1Eޝ^N}([>}95CmA*R_"Pi1iӥWzݘ^be<ޱPA5xN스Wv Zr\ y2c>n-:,&ck,f+3FT+r'az,_0y \o -ۆ쩩>3 jB 7b>:`|`^Pdc߼S<黼IQԠ9ۓ]/{#UD%ubx6{Q/rיR -@ Bd{i``pI( -v%G9W +GxVN !cB+yl ϼ脦<m9Prk4+&k8LǥÜi]6{4dFFUiǓ+6^sm{ʪYKϵ9 -ԣw@J9ۺ=E``;aS]|\)nlO$VʉV\jIz!Ienwn $ŗ6}J6 eoG;jb?* -PȺTuhnk_;i#P$2|&/*@|o]I{Ia&R(x @-B7I!^´I$ 3VPLD<g gj S47io5 ;4uƽUά+8W["{>~Ct M85S]VĻeu䷷J,?e? |yD`#݄=<+7^C`w:)[L|oEfB5QWHUAo.tvz>-FP `/x>F³v"CTQ1FkoHR:* *銦U>zzX.E@-Ǎz`AdEtLc'|(_HgP>r];*3~Q?'\)('D}B>TݐBO6s#4iV,_ZQnrȆB=Gtbm ^ Iϙ}4¶{JRnXŽt A1zar?obxC|~,sRnLhhhVϪiKȚ;]ڗ!M<]!q8f߰]njI:fKҋ$'|TY<kvK'}FfW =um:ffmOj(yڪTTݑ=TUy~竺>43&5ѱL@󉒐 RCU;˯~`zkUdCCD!\ze$i@ gQJX8mA|uA9KH7"M͜E:i +c_hZ Vap {,>] hC7MʷCiBKf97>4e9{z&+sg37vjI:ge8c(>CdY`V&3 l6E,0fz;}־ -M/r^اh[sž.n| NU8l%v] -EJQD4xk0?+7us^wU?]/(݆7 B#\"H\@34X#agf'k*Mjk7$s1τܳ7ANæT -N߭+c_φ`']EYJc/n0(&8d}L!Y+ܕGuQzs~*-qhjs?L#t&BcK.mV4ov> Cv@D;lHeϚĜܽ(7Hɸ `*%-ib* {=71<(N>[(C.>P4p -_gz@Pm/:\'z=F+y!<]t%x眈f-ԩT'ݎ79^w]xvp++✃_K;" 3U݋jYsr;Av!6<م1dSp1`D E[n> -B0 -!*<6w=̧u/%|<9!?o&OO{ -xVm8h?ўb_1NW$GJFsGX$zWbVfD]ckkW:OUӅ:rN}?-x;"(5 gA{%%O^O\着S -bvS||+?V$땄')ɡ# uX_ A:@PKS<6@S}t6UȶIx"T2hFŽPV1ҁڴAETx lOK;!̙-<'9a>;yRNգf|JWt/7M4s6:&/k7vFWٓtaC/ lϣX\+|?Бkץ&Y| +",C"oVx4PAsNڐ1S"sc™r:\W(87bi`;`}35CWB{^"kֽF3.$#~La-UЧ[Xi) -vQ_K㜳QGRd̷'tT?Lb\ņW4k{JW3I>jpڧ j6س!~Z'jH#<ef=&Nw rL~EBYۂhqi_|tnS~%~3"#=9eYHޮV$k t2^jma}tW!+I+]UQl)r=}fQQj9ÌB?-j'[F_T}g -%n0?Ty)7yr=n}U-~t0=AWS-wju4gk ִ"EОccLP-;IP.?;='zO;י|+]f_^p+K˷ck"<Y]fd.$5앿\5srky 9)XOag=,u⧸,DIlT:~Yʬبd 6?'bБOZP;fŴ/[nGiic -cyu7jië́NoȿW.̤r#F`sq)%*uHפV@ԏDUPP -RA<$B|tAxmVË扻bSul&sLQϩ~Ih ";ĮZk >*K=$>m k+.%"+";Yg:R' -[Hp&#'0NU?v^.kE"DqQĉfUņCy?ĴV.Hj@a&@s#ދPW,K{'=L''[edc֖ҐK$C8O6MBlYF6>߀^A}v!|K%hՂ >p{䱓!bz,?ʑ䃆ÙR=ċQ:92my-j}Fg^-vtysok!Wju d*JXq"+]m<^o.TAC6 /e>/dzRH"Nߞ/VΠi:^q>79]ݏE!*I \i#r02#og*qeխrȻ-%B@3=dcV|j tKXarBGGOƒiƙ`OYo_.x}Dtf\L&Tķ_|eq~Z)P=N. A*LpN#ӳ䅸m'<"s>Q{΋h~/+l`i/_ W=LI1f/hXKsRbF{ipuuk; % i^1x| o]2H~|{9{,iHt&0*$dJ p'{6-0myŽ"hl/~R%&8 -ꤗH -Ff -g9w$&ѧj|d\=/HݷQ er6ql"梗<ݛRIPd>z=BV!hɟ5@6]42'rpOhN6Cҹ -y#S9'YbSgz79ɓ]bPr eљzG9pn,(PST[ &UgƠQ ` -qAJ:aLeGZB%dA偶ެw#C& 3@[X>C=:oC8nWwF;˽DTyDt(9ɠX5=2EfT҄T+qfcK%pʔWM]vF_' a7KRBB' ϴ|+ո}8u絎sYy{ts ->,4Z}cEvrV\C`)yJ jwޠv]5˂ ,Vt맅^G_Ύeq -W{wjK9:*c{ 5/qw2-ʇl gxu7=Hˤ鿀W&7jS̬QFʫ8n'9M55ZׯNrva%`M*{ɼ\`BاMDRmL;KG0b6 X0\QAю!Ϧ%MJ\U%D!ZbY41-n)?SMMQ8Θk{xt񗝩iRK"^J4hhv<)r_F[i)$\E=VljQkW;#RZ 8mP T>P(F}6U]W([B9"b*RW*}}@Q-JڈWqv֤m 5^ O6Bn.}Bm=xf]]VeKX~>uBlhܣ$AOb$MROlgdOd*^Iٽ^ I"U{0kNv6+&(fF0W>pvYrіtFL%2m.wD(~.%ӱnjJk2)pI0g04IS=3EL I\1RTG!%f(ቺqs^1NKMZ \Ac:#KoO}ն:MءYlѢ)XwT&j<ϜLeuBdžD' R}DSI uҘb?ǥ kt-c˒OztPNVx&i؞LEI0`r@tλMI$9/XVytQF;LCC2d$<ݧorf5*!5nqHD NY^ڤa_wI16BF.tZc -]'Q?ǙqA}4ݹQ[yG =~S 9jPs2Ǿ\>6̅(f¢%6,,PB_wvp)g*-l"6pgsz;BXqkFdtxN-?َ) --tu]]6a=+6~ -˷I>'&w_tYLN1zV{. ]%z$Ÿ"S96;ɇMs H N>?בa?W q^t{͙ڊ6džCpoQ\TEE렜ZeHTzxi ~x,G^mY]q_q,>Y י1oQ[מzX*)!JS>F4Ku͌.mUᔟ)̪ܥKC+bH#5YQqPj?$//Ay~o<;fSHyʠ Xt6fJJçuWĻ{bX䂪Km-`b"Kٛ' cj<|y \U6X9JoJlklK"i SJ#2MvWT9wvߓG˱-R_Q?im| -`F8@|T9Fv|3vZY|RR08jP(nHr,z2֭-7Js}0BT0nuDv -$l"9YsFge>ޱ*Dr6|0m6& liY=4nʜOc,|Om=euQ)|e -li6]M42%R f'A xVxSX;KxI]EMDv ojޞ\l}*>T*;2}y.r Fgмel yV%!.?F`q5"~NE -1c3SKu!" -endstream -endobj -331 0 obj -<< -/Length 10976 -/Length1 2243 -/Length2 9625 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڵwu\t hn  ƐF )Ai$DF  sy^6w^3X `v '"@؞[ <||pA_ 0!@v 8" -PAAp`!>n ~7Ѓy H7"San>p3⮆ 7]l :4xy:0/ Av g sLFU}]#=<nn0(ru  c.ݧ!1D ҵ y<=wm 2s@Izyy8yz x`p'7o~`  ߃: ljpUnwZ`{t%2 iGr qzP -#@ 9EPzhOSW!Wf zsǀPO߿濗mz=U{=C۴uU ڃrkÐӁ +iIDąN0WW$kܻ)sB>CO0/:wk{ԕ49>wk[9wf;3r$~n07# -;_~  -.(l@yppWW:L8x!:W@ϙG/OD -h+#tǛCwB{Az`_CW)yctw HY#&vX'P@ ?#;P_K_Iw2:E@8ˇT0)|o xy028w," 3D -(W*xU ~y:8W?H YEBVy x Ai!;AfAA|P 72<4 !CtD ":)Ϗ vw>rAb\?럂+orY Ƒ -Hpg r=@dKA}~֭ݵ#=~cdv@>7mbvYW?߲`~܂na|i׃M%\2#JF0XyUPȘj$eP*3d_=k f{R -L{-1e #,7V;<j*dk1=+4{-4ݜL0Uoi?%9Lg,k^,*ozS̎ n'?=-th'D+FsL$EYΝ;y=.Kv[6}LG`XK~#$؈,bo>N;⍢}q#"٧b2 -x3`h9()Vbrݻ/M5 -b>80XzLGo.%gKzof؉?yE=I6!6Ր@SqqS*g,$L! Q7B;ERS3жLǹNN WxW0Қܔ!2dFW0/ABnǮ b|=Rd./SV4amLKfL@WuEbV.kg]P 19/PwpUgHd֪z8~zݰ/գ>TKqb/YS& E$< :Q(fɿ*=(#h8~gv9}k,OuUYQ=ag|G OlOH Pt+Rsv[2ȹp/j5eĈaHZYuDA5`}¯hp'0S*/I6nm_&`O`q~譅oz%VP7WgFrmN+YY/E^!UύӟPE]P$7!mB9*\1,e 5ߓaUf:^Ek[-%^s}Rly=.MY C%)4a?9;FJΐ#, /i#dz8A" 0}/nl,mf᫟97l,sn]K>'/8t֣mUJ\?!NHr-۞|k|M}}sE$lOq lb -[@xL⣿Cf6$K6WVXoV)UgJ[8+NN42B~v]$E;=KyrNjFDgS3jjT$WB>g^ZT=d]2:&NZvHD/GU^A즱'}Ϙ&mזmU20?l]YʤR_L\{8mCOg8kA* yL?{4~ea%\.{IPE"S ܤmpE-ś8t.ϼ:4؞9L=s4՜y9%t(FuWb"KڏS>}kz/WEݡFE!pf&ۋ={a *0.,Ȫ,/?C7dRrcė_~k؞zG 2\hN -=RYfAmd9sfd3v5+_uƽsk:ѝ>*~yvթ-Џf8Jrf9` M8wm֘pg 7=!ӇBbsߣ^ϋF^/|8ВeX/(r LT8y͢onV*w֎jsdiEl:t@K|{`*cJ ^J ~7W~Z8:X g-ppKurm a,nrtqo`):ԕry+V|Qc]!GJEOe[*)?;q(X^?ðnf(j}N6QhPCc5)1Wo<Oe.;Ǹ~e3hO7&$LQޕKcS.x!8 Rޓu_ApU7ˁ݄vQjZ= Lj^jXC/}ZRʕ"?ji3^#>lHPD -Ħoo,ND:EF_!V_yfUnmh=8urנ{;cc!G3Oɟ-'] Yyv'h>C`32\E+ER)5sO+ߞ6T i@#9,Re%s-LBmeݭŰo LL )j /(vڨ-掌 -bO? gîph{z}Lۡ"ɇz_:"e? r[S䒂9S >_eBw[0e3m3hUX͇`&?]cjy+U}Fd zFe椒\,& I(HTk:I34Ι>Hkh׽vKMX -0)$"VE|?#^| -τ]Qӽ!- MәЖjzZ7Ԏ<|'~v(갨܅^s(uzmG#ϙg_5dW$I q͠1`1u߈~!`p}ACnw F̐~VI–w5sP2*juK7?#dۿ'tn(Yy&⚪2/Wqs}}:ǯf&/lI~:k%2eua8;vHUu;K5" ]-Gg?uC,1k$B>؟1-8}ذ/}.*W>+l2Km XeCC m`dnPΛ?"ȥsEK A~1MHcS~JEo[Z^n!i%B-津\”-L+h~ /Y=L}c?hiM^FDMHƌ87L.xr})"ESfg^tUʧm{Ъj6T]Qwcsǯ>JhH 4"} ?m\gDa_u%=%@D:{$Iu*U8-"H`e; _)V텴+;:;Y2&=X;Z3aDyZ͖5s\ jS -z -cl +G ? -4s}Q:Wv!/lg'ϧI1~1,S;`]S:46aiؿ}I81Ez],yD <ў;Wx2Ry ͯyK՟B@^Z|i5$gejv->V܀SQ .`FWN97t҅ YsF 4c]/s9 ?Ј݌ չ񧂥]_&S1 <7618r|& %vF]KZdGjm(gv(QIM%)VUXke1.vSbO nHSM>YgW-^WP齢ɒ'-X툖 (zD4KÝƜEJ.`X8osۗ+O;E(JSW6?VPׯ \LNv%_,Qyxm~miz(?acQtt-kCH3S }Zw_a%f|R۠!K0wqTOFt̺۾C̪.! c+"IdZx;ŸͦSIYFӜcS<5Q1DIѸ|fqݻ¹xb>i֣| %Ǚ !!ԺFK~/ T QY\{itē+dcOiDNjvL&'/=&m]8-dFn&fƓDIa@fe&>07AuoHMsr [hh>tV _ -X:@SG38xZ7։(pU4'8:^70WgS3  G-Ř"X%@$win}i;43bzYL*Ѧ!%eʻ' >B`]bpgqDWKTJBb @92j؍vaI_ߩhln>:ʰɄ1'T_{Ƨ}=<[87# K&}h7ϑq, q:!I=׽q戮 9 :UEh5Rf`n3,=#%z$JnmQf)M-Q!HY``xVw.̧σy%W=H,p}Ӊ[t-2}~mע}(R/12w%}e -"Õ\ԗncTr)a+Ae>P"F{ESޜvw`PxN=c_b$q|Ln*EǶo_e>Km:8 $+|+e7ʐi"ҢMSW\" X~s%^l -16䯑߇Ɵ6׬^p{ -}.N5hd=9Y vIIƙrVBxrulmWZl[C6Y48Z"~l5!v)ePe͑HnM\)&|56Gb2蛻&d<{b[}N.>,oZ%Ɍ5KJSB+({y-6ꏃS :כZ%}Q9 uZedxzՠDl[z۬׮<~ 8q5pѝڤo7FQpw?5$]KGaR{otl~BX9ؒNݻ$O^9-/FW6xSI-kC}b>o9<&oi -aauO*ZH/ y;Ȁ!Z*q!hmb?to],)*-Q<Y݁'d+,WhA'~3F92VG2d -Vn-tl-%[IUv\lu5?9^Y`$Po;he)jRDh5Mu0GbDl4T\=ffjo=f= sSClc᜺JV~4k"*kq }@}GjX nbRx|%1ϭkN>g RQlC;Zu$V\{%> ~+eo{#C`MzzGUk=" -r|̟jHJПjX0VbnߟQv9Y^xߺyabP+\|xs¡ -h #jBnW]R}qd:BB4˞uLٕw;R +S.'-.2YU N],+s=RUe;)F3U#nc3?):WvR4vg (+j -s +eaXԋاjQWWoE_e.?G+ߒb8a`%rJ\MvB7m\b%|嬻>]Xls;+ܧߥy3칈]>{)/.l5H!'gՋ}HPauz 4a۴G CNvVwBPe-h}Ш/sQZmZ,g][Dd -Ḁ"qiNfxo.yt [u㘘MƲwTᔡ@XR+G|83A}° Xn8SMhp.>+ v-)zz\Yk{O -懰!& -S {`tB]!ƒQf<-nwC䯍(SXNJ^,:59,*ɢ9S3x ,nN^y{s툘˖0_v)UDz|92j˧]s$ǐK_;6D)5׊F/FqBE=)7:-I p7ծ8 as'ٕYXd}d\qz$gMqYv{^hEX ([!T;ޣ"޶# r4g3c,~fM2mJIU؁D}%yW~V#X[A-CTH 袗 -k>Glb^aN{^N&֕rJE6T:QЎBDPS]Cn\›?ifc%/_r[OktQE 7ʦ -Q)ԗԜg5دm'ߛeTwmU *ydVJvv\M}}<ӕ$а [io|@jU?\y?pA0KD7"&d1+Rz}i9Yn<%UEP9a  NfdysP4O;FUᖤG(p`b4~N7%qiih1z{09:2+O^c4oN"b^Z"}ڣy9p{} {̀9qpD ^Eh8"~Eay-@i[\XyZLxV6-)2q@;BeL|h ,1WtO0*uPc|i=}}m`c -K5ldJDezf>o._ /I[{x>EQʜOz6o#o[_vTu8oJdcMd|}3is -ǻx=[GYpmFߏ,-S:Jz ;oh3Xof4)=!,7_%>syTc\(Տ~TS6"GZ,ϦҢKv[r}#>w_}e/=’jgK7SsΣiy-5v[Ϯ| ->ot|l# D$aa@sw, -ɜFmKT Cؖy[?^X,V{^`Pjhk;dfaUxዯ5vi'0e\Z5e|_xqhRI*1Rwe??pl_L3{T8KqNjizT!MA:{ۺQ*,@U7i/obpKk=q01TE@UKeQIcT_KILC kku=zJ=8 "9t"/ Տ/]1UG]_%{ -12kJ[Pz] MLqz`?  -endstream -endobj -332 0 obj -<< -/Length 5196 -/Length1 1999 -/Length2 3962 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڵUy<'"l3Bg Q̃ǎY![H!BDB*!7({Q-Jgtmy׼ysmda&X/hA$Pa>`kg83}x -Bh2#54 #TP@PgD}A2AK!=ev  @qp$R0_4RTL̈$@eЄ֦p ")A8Mp;8`OCBL$`puF99N*pȱ3D"bjػM tuva~`q bB3 PL1Mb*N4 -N$I ~.8 -@'MFbh,Nj<-( ȂC 2A2} -&Mٰut8$ HESigC= VA0v)>"MbhKn~6H(T ÃLfp > =̎eQ7L&fX3bp0ĚL9HfWtBpXIݕ 6D?e  3oS`DF$8?zqGPС @%Ȉ_Bm}?"C 1GwC(#D@c% qP{(L,hx=:T G\ٞHF(0눣b~| ͅ 0\CSCʜ8IgX|Ɇv?5J޴QeW+MK -ٺ ,x=L-j偆cܮѳQA {M[}w_/TFikuk ljnv/w26roytԳɨʭ6#\MKRSFD_VNΦ^T÷/DfZsCmǂ`4Dc `a^8ȣR{P.R>Գ༗:@'ά/nysB߫qP -^(QB6l!7{ {DLe,C`D_U6BD &fbnlTRgsOSWmOK ¥N^DIZ|MMLt5Ḻj`9oցB%y,V;! HdXyh]:Jy%ၐIͅ~ɿ*eѮROOH>oSä[Ն^pgsRq2Hi'WdΨ= | ˍl +6~ALxhb@bJuT" CpO=ra f.5&a`' vos)C{pVM.08ϺYn!A)Vяu⚿QȳH-^se7= Jff KUJ-svҼq/Gv3kz}.7c-U`kݓr /U>+mq?عClHx"C}t."8j[xӤdO& q +-G#{_[T|Zi7*/ј{PNJ6 xK!2L:k'i;0uЌG~lcTwV΂CV)P-{lO剗yE5 h_<7+f_6=Qiʳ (oX?"]k"^D%TfNhPaqQDI7.G%8aJWh$]ր>dJ;ͫI}}2Nn -g>nNUY1tHe'GK#K0zr>v'u-jx&V,+!-ϡFsvvP*re[lW[՗c&-$oj=j#{il-Z6]-+Gt>Ǹ M0ueD+D'TB6m@䬪Ío}Oj:mjɜmկrZ6u%Ӱ7ue׳Fvw3MndjuQ|C֥- -&v V -3gd;e֕PM8 #ܚP )omNb,1>,%}RـlC/ΫsOF֪j@^+en_U=6/MϷE |x ]$[~:F*w4Ph:.9.QeO6[s|mO+3P۩`Zx{AV""ꗣՙ固{pkJkTZj24}-b팏U/7FNT5:nB?Vg⤃Iny Ć+ӇyqD}z^)yqtgRϗL`+N,,Pť>%|ѹɵ&kbdk\$k|rEy^e&):+.]v}`J~QKg#._yzniIã3 YZ~KܳSz]N1v.L/L楼Կ}ؙ=ύI뚣 w={#9mͱl|jnE%\)CyB8۬mԑZRXkov>3ۥLSs+`*hSBIVKCr|a6 -߯*^qNk>@1PVAtcdZoxǃ#+M_/$afkpXYgpM -#->v,;2y*ucŀLQs!0|6FiG|Mvt #l9KM毮|ąe|JL]?x/^} - ӍMHn&o0g^q:jS/r^)Bi՘Z|gxnCDʙaBckW;sFܨT@>Ɩ{1ò^5Pm0_5T6 E|q򈸙MwS_&\'쪢xQ͛XcxÚftG2;[k5:7 O_3q xEm6.|]F*U׌lKJSscD;%^eKW0[ gaf+>8Tx?3Cbi挏 3?{ #CKIm!lWfr%t{=;uc'ɰzZf%ϻȞogًJ~I|+,`cc鬢܄f/MTMoիlw 8naٔŽqqFp¦A#ZQxE!9wiJ-N$l$@esS`TvFVg -k`D#AY3'<\Gͣ1WV'T8tO.=:ܮx-dn*?Nm*$C/NTl})ѥdHy`}j!Jp铢ת:FE[j8ǒ/n2^uc\ \⁦Fn5b/{w|)LtJ6:v;2Wqu"f{W}AN}nrl2Ѳ"VjH>1VЏ6u[X*N?yΥjm5_^>ˤeVs Rn^y[-oWRVsZqeNr.A|K+Gt\SU.U qGdB|̪9;* {z-2QK:M)r0Ӷti]2glٻMϝwvfA8wc ?馱_"ϴ^eoB"JQ76 -endstream -endobj -333 0 obj -<< -/Length 2142 -/Length1 1306 -/Length2 1303 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڍS TYUn3Q>waXvQ,* #B$U!$qile<- - -*.-. , "0gΙ99{LYq.1šA?ĢAjjb䃝jQs .C`yc`B ,ud9B`B .sXcjK24JH8G`γ ;1:p 2c&|ACB=) tJ9 EXXJA "GdJt%5(F5BT> -!4QI#2@|Z)V`9Ac| 7B12)Q, -P1rhD,a`b9Na%a. Ku&+OΓRBNb]t]͞HSuy2G]MUMb| BJ1!MO(lpg$ ՟c#{zxy,( k6X0IlX`؍KC O,k N*柄 bC<>Fo*EB2#B,KPz#/GBׇpŢۇ9h,@ p\&cvTd'5am,j*DjIL ĎiN UZ eTH @LIay -܎ɑo~""%>G.Yvku%'uϖId͜yfS:Ggi`aUM䗗O7xO?knОxI sYpvN ׎t@zEm*uWzOkpaz~m.ͧ^瀌0^ceɂK}śl}rle<- 7g#9_SwkҤqF+TZ n -VRi&3q{EhKo!mx"cm fn8:پ! . -b9_&(=&Sd97G)9ͭΏ-o5)3ZܣuٵjqVր'vS<-J^)gay?\j2/ .hټQ!#X52ݰ2%_;)M{ԏvܙәeDMmՅ)-C>c'련nmwC5&S U\fYגo߾Q]Qa _95+deOoug7U~3νXl8S[!ʼkOPpҬ}>\es,w]y]}2S4ܜk(Sgj:>b%+Y^ vOxp<ңU&Z]S*՟chW&^"-R"q 2-k"WNO(l=v2xX > -stream -xڵWy8m۷ .n,34uAĘ13̌5{,YB(*ɾȮDi!B}C<=}w1s;u.#"`f!!8z?K, &]ʓx1r]_ hOnRR(8obBb~$WiafPX<ģh!E$ b~mO"q/_*iE"Jώ$jc xD&NXǞ3,~345ӵFPf/mBT!i TyeE@ͩ.Mps&1OK@vWqjzx:q@` @A@,S -pŝ8p$0RWH(/ =++-1*,Ly0G7;_0_ M(+!qtbȔٸ?ryp(7POc0;7%P?tXĘah_%wƁB@]]} P7y! -dQj00*cbJԈsaЇtbxސ|!Ȍ½f盫4VBg~HO_3ڽJH^C]%`וּ9$M]oNG9`*1ݗӃVK7hx.q3CE}k("ʕګs";UX9'j=W]|*5hP3Ҫ]b<#1 wm%Nhnu8 YNi51(bZcj:v̞uLwwKrP4zd| ŋ&#?DBM/Żp +$."uK ޘX(<ǒo l`u!G;oyY-S%p<۴$Ѡot^faS [XݣɂǬGIun#ķKl!5%~^jc -҇%Q +7^ޞA:LuoЯ11?,Xo޷vt"wUP0T 3vw !'Yd}N. X}n 9aD:2GvV,>vU:oo3[lˡ#KraU];Ip ֵf[m-x7U1?),?>E:h -]Vos#Ƣ@@zmkg&rvš!}0jUL(JEvwMI5+}d\ \-A[G]^٭l=6Jn<-}S$L=;~gKZLXQ;m ɮ" kAG64.۠WFږ_SLJEQax+cUI'"V竇.o:r`[],1V`OŇOXMď|9E+~D,MnWktfRrN(5D*:5BtrcW3RUvD-Hcf\ G/\w\ouU8 ПRڤ;O'45pQC I|8VbU{ 'ORMN{vḍJVͯڧ/,a:Y?HuT:FG[e2$f̤d=K7𩡈+J#k;N]?1|S'+Ň;\_mXQJ]Q;تN} ]ur\]=.oK횀'66ʴJ]rGStSxC7`gJݚo-0&=ieo|S+o.jߥ֝Gfzd -eyS8z Fi{eQ[Mf1i]諹*$ -Lه6Kg4s+,N))/0rZ[w&lA`EO7c8CimVVqV[s*F ]eI50c0{xI=G`IK!P MM"hץ! ?NK\ϸF1GxYǽI+ 7/J,=ypl>W-@,K]$=*m+f1֤*6T|crUAp$i!Uwg<&oLc4Q"sL_0y/}RkRC"/r[R7>Z)ޤ1P#nv -P#"=`TOچT1de5=h>߼)RHxKHQsz)#ΣR :ܯUPѯ!Wd Mo,$dοlp*ǿwL\jxxhQYUKgf8tnmvC=jhk TV -*WPoJ -d^Hsb|ЫceSӫqS*"t *+v $\nd_VKܫ!|lփ|mc22Yc:"s|-"M/?%j+'PY,-ƖE$5biyD 'CL1=,bh9eebPuP3NIbgU:gDjtExjӝT[;;XhlV5*r -bWKրkeU_y^tz2? ? Ɇ~qk-' -zVɲ?{5kW7}=;z[k ne Y@/R FBʼǤY.6zEkVއT/\=֓q0g:[t澶kq 'Y(=>0Vnψ)8軋yTҖ5M @w{.ߟaA{7 lP|jLs| '/N9jh |NyĭX(-!fP4~O- 2<&=SpeXz`{O 7=ƙEgH llϕ ݌glѠD6Cҭ߄ 6Pdg{3:]xbh3uQ 9|ya6)Ց8?}mwM7Sz1BOHഋ[i r+DsU|Y+/2ͯa}E-af.l8r.}`ׂ4)}#ϷϚ*9i'͹tJxk.;͏>*#1N)2hلU!CQwW27}T8(](꽳د'?Ry@f?ZzulO?H $f:8RP9*A_Y~_`<x.֛>?m7K* mڗ]rwvx(w(aޔGg @O+1?OٕxpBy Lx:c.j, ToJ3d}T3+{ %Ȏ%g<> R%ûɺl3Da!wz\ MdwryX7qQh\k7qVRɽڵV'' S~ܟL(cv*h0/.|Z}BͰ9~ouoYPOv'׌7{n,i]f#~cGd/n}dҺr()FUQfvƗF=Xh;ȃy|O{jԩuj~ha9M#ŕ??*! КT*>6 _v"+,]kSc]WJ:[x24\V%JhhP>T0[? Ҕ -endstream -endobj -335 0 obj -<< -/Length 4667 -/Length1 2082 -/Length2 3474 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڵUy" m~~ { 1 C2ZenYT9#u񃣸[g0:448bSeE]~rX3m{imO Z#fgŶCV?|"Q^pIӄ9`Vxv͇3;SϘ>)6i&_®hB8~PΚǥ)šo4nE 5YY?Ɩ dHs^ |CA\5u}5݂ϤKb}.k%-vP/i+wlr6[j5ąV۴HҾ\C̄yUݛ!q -_( 1^R4_Mj곱) DCJb}DE0zڌ+?]=M{ Ih=ﰂV-F1Z!^x@9&Uݠl)Nn]W3"R&Ooq0T -e=EٙYcD B%P|7$'6K5޶/emaVs|OHuaxcCƛ?7Y` aa]F"mB{n!q8FVQTZFV; LDؽ,hv288O\o~׹EϹ'zEnړC_D/5Ep%'k*]1,k_~-կe$0TrX$ i{Ϋ:sWQw''IlvU_@VmmLEŸ,,[}.kuB3#cOH2>BZ7F]6YDGhjݽz~ \ɝWs+sJrl*`2tW{n2?cȣTl4o{GwPqل[ըXHt 1/';,nwWwXUʤuvolɁ6%?h@0&y"=To["'wOqւW&>Jld*w~ 5]F-tߵޱQ2gv߈[nOyU!pJYBaڱFnkMw6wjhmxźatTY4-3;< L-+b9.391kߏ޺t*+MIRլi`dûPc5&sj)۸knN";a=C[Q2qNkXkoKUI]#!˹Nvl<6=1vSf537r-Y7ETcSY3gUe/#WsHŲ>DZ 2FE>^֐rO}dW""dSbH'><5xa1SSd7uQn86sKaܙ6$ˎŦ~K&;#mhp\3ntʰ`z{S˫AaQ$P+I|Yܘ+fmL䲮ZX=UȫI$(splOi=yKuC-FC5ikFoF-8M:&=E<]z"rvf0I\{yDdVCWDm}~lX!y-aq]bK]^)! X*?.=ֿY8y{]z6HbN#,4勒/y*ŠO]IsS7e;xwEv$mXFZd4MK -!DO|zS5#+Z.,9LHoh-K͛*0/y(rcCߪiٽN/ ވOV!Q1O"٥ -:`DDB:ᵬ35LH,#]`D瞯^[Qޞ]5 KERDJ4e3{ȏk=3z@Oweܮ"ւ~mr^,>8uJ"{C)vSڮ:8 s't_y) Y[)=ywWfo:oMJ˧lO;U]Nj?DyqXK.e3wҷ*I^8֒lgYAw\]psit5w*iawfsqog/S:jBFqRT{sE1{%krp)eUSarǕQyD7Z5%2R>HIgo22CvqP(;o-[.:{/d.tg/.r 0#KI Esõ+:#IwrJ)ҳR4s;,"Qv]:E[uQŅWcr,d5K<d _Ms`X;.mQ um&MngOX[VH9-,t*8qY W >?=aw5|,s/M/3 |޳Vꫪ믺?e+nf[}U42/ue[_i -P7Wc6g6Qj3_#GDf^;9]=1ΛY.̂ԢOxJL3$SVJ| -endstream -endobj -336 0 obj -<< -/Length 22558 -/Length1 1853 -/Length2 21352 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴeX\ݖ5 ܵpw\ w -w@p %xpܧOy޿UWծ5Ɯ{oJRe5F3= `glͨ -p5v1p PR9AV d P2}~h"P@{:Y4,\@&.b=D;#_OEƦ6.6Vc{3,@8Lƶs: @CMBU FXrSSאb(K ) 5@-q>+Hk+K2tv?r >L͝ ݙ,\]@LLnipwp|\ -joQN%_ -@hk$/G)?>p$Q_R'?;c+{Cd ru}|fJsuvCESuؙݶ _s+[]?:>cן< ` G.'nQ''k{w{773[y3WGf {+'Wi@, zZ2 [¬2z;:8̍m]V rvzo]!r̬LADh؛;xdߢ?Jѥf394%jkhlϒ -\imCf"i4SZe@~?v;,l!=QH0kꈪ(?GM0vv6D` ''f@`fw}]AsgaϾBVdppu7 `ff0;t-ߎ9X u?8>9~ q `vp| V^xY:WAv`: uU;Wi?k5 PfTAV,|g?>Ow/kQQoFv#/㇕ؚkk=˿LCS$ -fʡ)yN+C-gt?dR:K% }hM1S5S#@ɤPEF{$=_gf[|1@cX1m*Lm-ڽhc `im'L7g=. ۍqv(7|‘&~uDE/Qeu-fSo:A2!ls*g뢻i?YYb {:  ef?oO~@(˾.^+.˜60x9ǴqgkBxX$APPזHϴL 4]R-0  Wr2 -n&[NJ'jtCTƽSAX9ml!Xkŗ+v?O'9CNIo:uXީmS'PU=s-'f[>- AO,bL!`ݑYVpME}c_ =)Y;^wfC9]nC8^I64}.? xR#L3^ݎ"4Fr\R \]HLff{\*c*0K/e%P.p -7σO_&WM 7첋<0c՟G-%:Ƿà$3P hXSsT,}g6`2K3, Hd[@6*>U`{1;WkjM q|]Z0XH$ C1C> "P0_UrV AZk%pQ՘7s¬Yϭ~Ȏ,ϯg1 .z^"E<0 rυ'`, Rɍa2Nȴ™qۍb4XRX9[B?{R8D鍲 .=<&X7SBwV %`\/6vZ:K2qRt9>j=.',K:'ơx.;Ȗ fYdm;N'*D+ ?q!`-]NeR#.z:J}(h/zzy٪ xJBeC1܃]DgY/INvvjVRɺx80uÓsC]NHtΑb'ߚsoF5ccxZ8Vg 4B!rJeWyuܨp-qvnh{ofׇ֩4unsd3u ;S%uCw~9s(%%Gמι -c~8l.A*=ܕWɸ( -޿|PqbpRPSȈ^79iXlK)*`.,3nW_(d9eu B[GH*SmBտA5n5|^'ӟ&R+Eg1V2u4_e%C^0^kNCIOGc*&pS -N ^`z5?mq*_т!ڰ8~\g>є8n~nwG$ ݻÝ/{ -79*ECށ$N- -QDkˠ݌ -DZ 'ߘMc^ |.2mޘ &5\45ٍ *nTuw=vDp)8:-%JQcRk˵y& &$%vLÅ 89u")i)*r("W16X<'Blqi-O3߅I#QC΍;S࿐.j/x<+CrFnT!뵓kL]w+[jy1MXOIQb%!_mΘO6  Eϑh VU\9M=yyW ko_ۄ:wdZ@_Ԙ|{)؁èM`s-),a$6 @uw !69,(q-,Z0DY/D+PV7~5u -i^3$^>݅#œ̫ O讜*bJle؊׳Lp> AmX Y\{/$REpWXj]y߁ vd].AqzsFBiRA|-x7Ff ,OS0[08Y:u?ò#W4U -31}:;4m |ěis hz^3 Brx뵐Kw޺Nbm ]|S,Gvh&DKV3slהxj>Pg,bTAUb3 tbRƟC&l]%ڷaytS0Qm(AE /&\52_a.| ߧIcE-v")2Ğc ;m |w:*O*nm7A(q%bnQ[,C0f*=FM'S9~g(+nŕH]EZϣ%{V֌D ı{A, |k7Z"*,|(h; }d$L{If4QHutji!S5~=Qi, =4-DžhX-\ŪU12BeJf`Gc! A)>YUۊRh/`ӄBsE;ᐾ)Z߯U[X -lax?yƓmڸIؽsGYx$7\T牄 +Sѯ`tL˚1V"k(nlA&o-%~]ʻNnҶ,lRZI'NFQn!)6^Piib=^ȥJV4<:'9P넷͚FWH t߈:%x>͛x+tQD M3I $GLw-fx}hGC7b Mޠ]^ -b/ޓYkgD$vҟd`!ۀ̀pY*A;8+,6Ġgfc &˼BVx:ljHd-0K _C/X>M]{r<#VGXe7_5^JK/qk䯁>+*>c쳥)lt:*>[:a?g6=KrBR9㴋`.1nd^4Jm-EgwX-f@!kT; IQJQ!\m-98g+3_ٞARҥG2;eR@3kl+'9xjD+M*> :~vߗ+/d^ߧk֠Y CoTˆ̉9nfl A}hd.ěiN1KK\:H` mҷt#ҨxU%\$Gessnl[&2x곥gKܜkȜ16>=J Q:M^鵗߳o&\G-mL5nmmqGTCM^*u$ yP)q'凥ڿeCH፶LR>p&_Twf]|":z:A_܌I*Yg:5m~ScZBa9!ݨҩ!%-v><\]r -U@NikVsPNb; Oԟ -k 1Ed>[uDvE7,w՗o 8'X>]bwoQ`~ Fsl t L Hm元 }|^r̴]EWpB jY))[-?|僠ߜiTYh7ȁAVnVE੍o92 "u월2UIB9PZQ&PWXcm'+"3 -kB9u~ JS.\=*Acq~|pAcqY>`>=Iu1 ,'-si7Kj.wG2vM^֒_G.md!S 3onIDp{5I`TDBe cc5B"z͸_mArE1 #}.BkٜuLЧh.;g$ǙYS-~x/]z/_W.JqlєW.Λ4ebCo٘M}U!=$zp7h.`#ވ;TtW81>rMzdY )fv8܎ʧ"D]i}zHk#0U՚Xr'܍No r)IEI$yQ;J5wL&Q `˛eg(Nf{O֓ۑt.u4N -lП!:&S}=8h9g7Zmy=_ }B_Vۛ4ՠޜꌤxEpA`LWݸ#'U_HTumraW*y6. -{Wv\o}9=E>cc:$L&U3.:!H4Zmʸ}Hy"=TuNGi8% gAC|Zɓ_sH]{}-c\\ﮒ3XWL(AJ؍(LvD6Ze\ysB@- O}m1Z- -~{2usz#ӋMrv-\/k6]? ح%qkK8*+n' Z!fBc|0~fuo1 -X|=Az9X] ^ (4nl4 .=ƻ&R(F-/$?qe) RE` N%k!cVEx˲@PeF nϦ -w^d.vI 'ӂN,&!]̗֣uK q#AFm7t=^'U14csƢr>\RGțAzǰUu^p,x#I݌h<ēL LI^:$Y1Z XZj ~7NŊ3ē`}$xd#^c 6tUOm> |Qh -刲&bAޒ/Z2FU[ `9ˇ!3_)Z; 4T\v잀#Rx$hT\~^xHḿ 0$) 9,2G_aepĖ׶VsU,[R(H?e|DFOz/4~ ⽪x'IǓиJ}VP}!;si~E |ujXIO1IkAϧ_hwupɡ;8M֓3j4'$Džڳd;i%؅mJM] Vܥ -u๿аP˓)w>V;hAގpTB(j &f^t~h.IVݭώvZ_P!~7.N `bbm]2tw-V 0B5)s{ê\VjyXԓyYaZ4Hrgr/u-T{ʋPp.'g=Vko%rYr.dy͍/kG%8r;sWXmz[:y n-> 5jf9N$|cw?ʮ'Xru8~G`/{ϭ:9G'y~&n+F1(ieg)͉" -ZIcRQ¶FIȷ#0@f콓E汐Ę "䝮#m? Mh~h]R~=Q K|Qke.q'ns9w71io*v}:($HAؑCk۩lY˴5fW<VrlV}~zR=NMy$bX`X7@aS -ؙXOjDna#PRy黟 HP+R;q8ynzeN5jz1zD +=[iEʟHf}qA+[ܝ}s0dj('Z(9&_Oz9'ִZ\>;lH!/%2`Of*@)[~DMjǺ N:WN>0%\#Uڃ=>b/ ėY˚gx5a7.2w+^}G"FpMHod5(0nbԔ ڍac)&`em%Ooi^~G ї O)MieX}]qHGlMx8gx"̗ ƣl#=E cJѪQi[8dSY<.r:ڻRPbO'w4tN(_:5gciv?Pdrb#xbqicb7|>_蓥Rj0wb`ae%[:oJV[:}M F(օѐG"@~(9̵J"kxNp]b0 _z#']PL~q[+&}X- saU&=DɕPƸKS)# 8AT3nZ.7a&[&1t6dH^,n\T:(.輶 sc2k~޲"]P-j%9N# 3r_-D?Ei]K&ޓ)'C+%6?b{ X@7ZNL_@CCmǕ[}čC+X+&^w U7džSbVH6s<0ur=@˳Ʀ[Q9J%jlzDt "+tػ)Yd-MQJp.ssjva|*.=>s>F!Ad<@1{ԀEdoK Sى(Łh\/!XSrN/cmkidH\>ZVnj/4R3!]Lqp1Y >YQ28njtKM~G>N A_yL 5zsހ41͞a|-G+:R[Abh[tL*⑍P'DUp5ZKWHS7[*229&9M_I i餥VB۰oQD_qL7_7by 0ۂZ~I=W[7JIF' VjUuthZsAx#n41v®UgMxW%plQ*}_j pB5vX;Nv\:JZ_EOopAr 2T*^/ՍKɡ rOCm+F)|"tFN7p8 dRi'[x`;^DʭՊN+ -sb 6I2s<׹z;dEwvF=?uxtk6Qj'eT16Ǘ-]cl&qmglhVڤP&bS~`.|MC QRF9k/4uJ=#LEV gh0# [)nC.̏JU\g(D8<Ɠp&~iPqXhQxy_c)iёc 1B~CuQFo6bB` -VỸ"Hߏ8mۢ1F{ lGR={mYy23Sp[` fZ᷆Z咬9#OID/օϤ+5l|Utd } gwN=Fя}+q*g͢5u]%U"3VXr׸v2:euf٢:d -^p M#W1֯$Qң2hSg`4&%b͗dqphYҲB`.T>7 _ݏ׊qnq>nqLBgZv4z`"0'tг?zp As𺌍_O$Ǔn q{LдeʽCll80% Hbaj<1cDѶqL$)wDžL хǗVk^.a}(xܤ&sVIOSZ}/ճCJIj5_3Xa]뎻W7o" p"d:'f\FJ, n_w3Ώ<BW\Xj x9w]QN؃unm&PVa Ƈr: t4GGpFq̣O>%n͖}tr|Nx0Z"+PpmRͱُs-WG( Oa־ $D!N |v%G+%0ˍl.KBéTjyo!DѮ^;ȡO.Tq}j`& tn$G-`$}!/y#)=_e#ITHz13۪DMT)f D!W"z{y$Y_L^4;#?^8 -fǟ`ZztN>-Æ5ovAryT[<<3њL o3)jN<~B0Ǖ:4\FnLkJ*.Pv~^ÕcsI#wMNQoHIR^ -M\G){< *QK6{Rf&mOc" ނ2 -~8bco'A|+fgRdҢ,xuDZ@qy/_l|RK*peZ»bX7,.gӰ Rj-:/8q9Www:Rʺ{ک,~=E#7V™4A$u`J]"! (1;oNr[z4*[ '2]. g*Y>iΝ}hqC]!_)y)VܔeIE -}fShllߒK N3wSXW^Wǜhst"q`sx>e9z)mՁUKWodYң+ zH`éu/+ 'cOl\²^6@dڦc:B.8O>YR@)cZ̎ zXVPޕEs8!RTjVАBLe S -N+fOy_Ā_!5 |}R.my%d! -;`3LetT/s"LGSct(q8'¬)[k|*"xw^L>ŧMp[|-갽z/qy[b0ޙSUhCkYj&2訋< -&&0HK<~W[EqN)O/z9^(lИ1Yĸ7$^4kd`&$bt&s_ʜ961iX"ce8C"TgS/}n1]O:pA:oe"ǀ$7~M6]qM䌍 -"_4Ѫ:<\4*!JUN9-Jw{׵v8p,b"5 {1Nڭ8hNgblىosB( #58}U%V#OJ%%Ęar )_}ҙޜ-R1nĔ&WQbMυ$fPA|C*(xMJ8A_ۣxUCZ4$’L!I8p$EEm{<燑71~qS@ckjJͽU%dXK -$/&eG`\xbuDHE7jq:߾z# ->]޿C6=~)ĖSx4Z縤FwHǵ *Ri=.@㌴}iLof{_.i,IlRz\/@4X] =GZ^zGRcxԈ0I3uToTBt% wx@_: ;pQLe)z-<8vU+ETgwz_+h!.*l5A gvW"nVBM]Pgo rU.n -QwMwxw"ç̻nP,6]BFun'烥SYkꋧDЬ9D^:rGd*_P4u1 ]X %>ǔa6q:=\J2ҏLgMn -K~^glKCխ^^33mR"S+\U%Pcq[ܮEM*\"29-1zszxa$[yƣu<|ngSt>-& -Ҏv*""uE&]m־^3ϽRuHl/F(lmSFߒ&x,Y*8: (6cKIwP6;&% GkQsbpόd)n aù.$a N+7rRgv -yogxbƸa dK0߳X쒲zdˑ'q"-1HK\1uT>JKv(YfɟWZM+g.q}3_*Hӝ3烆pI7z` De -VkI:j$/l֧GyǴ} ͇T?]IЧD7hSl䇱]gC@.YȚv qTJ^mrj7Pst_bq\T";ljُ;<8D2cd -< pHԘ'S^lo 1=W[i߀D)=96]-y/|yʫQMfƖm?Ĥ\P޻װη1ij"C j#< + b5/eqjĜ'N)}L[=W73=eqӴe-)8\zOG-*X>"kpė Av^FzY5ՇLITiyw g18`ۘ8 ]4ۚ8*Z"+h7]IN]x57ҵ|:V]s4^}H|͂Q$T"?Q,b#M4Ũ[ࢉ吃G plz A|>@;ַ8R_r,[SZ冼q["ZA_j@=}-.؋Xp7wB7U@͠PZjfL?VI4gI:Zijg/=+[(}DY(N Q B"8D}7^8u^MLnrȒR֣JW9~Ժ&⸖ ޿=%EmnLl@s1YLwLɒC7j(A\/XLEG:yqqbEk#+tP&ql%wN:秤wpND-.H|%Ş }*fUUD+z<ʄ/]:_ޣϞu6*rI1#![[5<8l!9ɳ,rOB ț!:][wK -S -8yVyƳԥGwt0DiӨd?xXs[)ԛc6\;jL0jT`Õ)Uۑ;?ǃǩ\2틽uSXd+{_r8_Zyx@?ZRǻ9\Ű˨1)Go DI7ʺ)>Fo/.;Y.gAunZd ruJg}5|5e1%ؓgm[+F@G)u&Dt;S -pv-bDCÉ ,1e&VL~?Nu9B -}\}=̲)Fz!8_&{#]a|4!eA-Ҵmӽqq[d4p,B}U[ 6 Oƣ<6 IU6‘ziz3Ch<=K} ˠIN["7>|e◰ۛ e3wH(=xpSɜԤoM@#}^Պ11;tk' Ҋ=.hwnkG=lܜŋ% /zG+ Rvw;yݜp*Xf76IlvyP1oL)8\fIUb"h"ՙ;0+jZI݋*b:CdD!+;/@"'OWڂ[#[d:"xf5-MUG"[ݸzK&I'cx88uQs@W:lD-8SL>~\)fv /,q@W vQ_Yq1ʕ깥?(!&x *c8vf\C 3UE2٥*8XI̹+܈PW\FXb"̻<>7v%36uIDn~,ٌ ԨŋE +u+oSlUIMB2/\HV*/iu~nB&*CvCFI]u oW"D;m_֨kt}3UҌJw-2uN! W/Zl"7w*z$Xa`Y'R7| -9G}_<,շkr^KZ`%@ҏ ӻ{+ @Y<ĉuWd/#`b ;J}1^<'Hjqs%~8N]0OzF_)O:ZIUTgf/ҠV >AwϮV}$Ґ%S/]teb^6bwDQwΌs1J5K9ҋW S!߬e~m%".? U;\1}bsW [v{͍a xdJ4aק*A»*#hig R^IglCHP0Dvlk8 QAϦ`#ct>$(X1՘ -o<5D ӇN;rGQ>ZH0=vPF 6XvcW -Pxw:ͩ削~_@G{`?} {2RӤai'VⰙftvTUJ *IDZ`L?gD(Foz7CR;߂\Q$I4N v> -stream -xڵTiX.\ -jED԰@X[i @AC TdcLKEEu(Z.B\r.׺-rgVi'O29ߡq4KC01 ͆~< |i<8Y-ApwPAJ eA8>x - -'J@Q Fa% qH  -%A*  -;)A"C$d ODfA8$b*t6p0 aD$8I&8<>rt0_P`WXabbrZ B|+Qb~HLA,7F`4XBȶS9hDDu S -+4 =Y2 R`J) JXQbN<,@ND "UdR6TI AN֔M &Rr# Q8U Qg#q J%كڥkflH3ubVeͻ-PUa Ad0^E Au66"46Ft\Mc"NQq&U>Bc 4h?y%*SnBIUaW9Ɩ Nu! LP@Tp6"%KW쬷( _ FD8!zbq(a~fk+98yЉ}r&V %7pܔ^!ja'vj4$Gd?%.&q;q0M!$s\2I= `2^twOI6!kjBˍOIQX^:L03=1;p - ft#h2R eP xx{,!|1pNLI!nrX-NyS {u|\IhDL\ohHz;a'>%ӀFoebY47y3.~'S4yJL1tXDu-]j穼C5T?Ze1vw4Zc*֯PE|⟐}-ZC]o!{wȏvi0<.\ݽ欽Vyui(nXA~z\h}|MCCko͕9ݳ7Yww4?/\Y).bw>׼{H g|U?ֵFFdd8qSٮ9z{gڧ -PM/ -f|ewkǥǫKggYf(lW$n:4׌/T~V6y(m]^4/N2-=_H Ԇ:UGn7,~Y3mcתS\>YqrME%7ee_Vz R %imAqPo7!7ڌFy:a_ЦfmS|{%KZ>DiR{}II&7lDrwA+R͑v^ .lؒGk찎2A^VCnftcZx;D֒^k~Ľ9O)k/i6Q۟f}3:`OSUciA ->yf ˴!2+Śu83Bsjؾty+m?zW5p;cf907u]/ab3 uG?m=h43F10늏-6!1Zm36RZw~)]F -{hQ}o) >ooXc53Dw6C鋽Ĭݳ,nwM//e[ /-Ǥ1OE@d $-zM{g&er MHez7d}]w;/~d~w WI:YAx9ˆ*`:h`vA㋀5ӓ VwEV d7X̓w uÂ[.[>]?toӟ.w%9m夛-5rfsߒP~Øy?:`aUws{JrNr9P~YFy=F+^ȶTѯi7bU uX(ZQwx*GQi άXAMߜ(i4ly[6PإWSYln4ŁeC|{kUKBGiұ[;;{n9s48u2E#Sy>3 gi> -stream -xڴeT\ݖ5܊]C!;L?Wo69Y:9;"`fiY +HK2} AC? 9,<!~vB+NvL1 ;7z,AfU7ugRY:_ |@g3X0mϤuuHeog03qX?\gG׿04q򏍂Oui_&O&ءv ) IccKFH,#[K)T;T_/l1 sNJRl>б{fX9#1& _0ClLʒt1.$LL-AVN rpX>FϐAvBrr?,xLZm0c~D,-`77l&?>a:ZڙUQ{3#;'UK7rvtaU> -"b ``p2s}Pdc_XYl/y݁& v&|Vi͡ES<_4e3~| -gQIJ5)ll%Lޚ*  3f/Wv}9+*cjOh'~axGN[پVV2} `iS\Q2aa( }o76]Zڅߦ0u* $5{%ibDd΃SB!Yrz6E03.'sDA0Q*^^T']$8Sj -տxVo -7QOy8H2@/+m hYg'SA3bp#*.Q9j.G$G#h@Z ;UyRYpEʐ;M./O4 -"x}uT?&-Di -ؿzSG'1:xǩs-.^j@Lhw.iy&E^-S6s) -ԊQC<е|pw8QT{Dd=)nFpBT\E>'ɉ@Ύ!K|ҰvP{k7YǶШ4.{aDܧ}T졠t??&ZJ?'r}5*+ ,ZXVM}!Prla ,ݱeTP$1\"gbBi4Zf(X:l?(r}=V DGY@ɏ;foH<9~g{)2',Yb[޴xO,oisEf/?[D`݌~@S38{ݞl@cPGiɅGǒC RE%ض-WuaTcY1Okl+'lʞ⥞Г>eڒiz28r-x`N#F% -B4kښdg|~%cS1&3,J~rP6B=yMǖ,2鳎"'@nEǦ {_7YϞ*Y T(+5ԡ=OXS $ne Na -}wjtk/s]Ob3G7 0vQ=4ԅ+^fcR|E'LD]./Lgol"c6vYSu`g4VثAMjHZ4=l % >+ -"$ pٍ(=VKeDŕvL[ot\ Ql*Dk|3һVs9B~; -f8n(o:*r*T5ukK2hjSD$]/ ̈D]\|`L}!Jn2KOHإdA[ԟ(.Pp;.!.´6aT5b+}u߮h6<6nS;Մtq!lr>59)sƝ@[J+錢'?9Z -f -㬟%e%z&ޒDvuOa CQ|haX;ࣩ %Ix=aͰ)6m!)H&)>uR#2gx>84> th :T٢:Q) -9:*uRuf4Sdq^~ˑğP^PIemKVH3<1*Ku~vWLkE>)&#A28>}b .Aem1 #9^pUt@=v.ᡇ`%g,Jnh +X*{kTM>=v;iJPAJ)Hb) -@Ns*<΃N'Ke.Q~msnI -%_?5{ɹ]ua50s#K|LO=eQ0kjP~qSGcxun= T1[˽q aj9\2ҶT6kb9)I@Ͻ!{)fl*F+DUkT -VjڤYm3nX$j`@m\`QlMܳ:bij!1ϯCֲ]qr=_Ix=w hKBL#)F[?Ac z5e) e:dL}?βb,f-F&M(5{F)[8N{=Ss87P닖:aM__%{9ɳʈf6Qi *p][DSdH<Ǚf٘ɤUnz&^QEj:*ԅHsB -8zCB=xD{P|Kc@Cž7<P0ԹޜA3y"BU&(ןBt,!=>o)BٹRO, -XIbE j#]g"62);ȝ;i¾ttt]GjAGӄNAKzţ/b7EgRŒy4 -tNU?qe>.;RF+콉]%W[!TơZa:i\GZthc#ݞ{h.B&ۣPlG#IO_{=DKKSr6Tnk%kY]"pnmgqZRޫtlwQ3cZ-5&/ -y k$M% f]W\q@A%w܌}fz -θS$8yB$5j *UR3>g$CK!>WKze+Hwjw goPW-[$b\Zwx tHdnXMC[hDcM….]Mk"/! RVevnG#MJ_iY |8JR-mz/ l>ŘN7( (l+ SdM_ ؛ 5&%=Г+U}i'Cuj4r0~7Wk?YIi_f#9+uӂi6hQH.m=FO -$bP.; nܹ1E"Ѯ#VrjHXgr9(Rɪ{z>y-(gk*7h᩹lj6>چ3U& v -UuFYF ]Cy yX n|b56dڣH¬SeTsooAڋ`nY&H<`R8\D'(sAK\{^&%l$!K{0$jbnXat~7Xa*%۠7^~;i -[ͦ* J셁ƽ: -2&1[^" `@&Nd܆OXnpEh~;DQ Wݍxأ9ӸY$1c Iq蓂' ^Qso3ncL m9y:RU/&?2< ávt0X\6%3]*Oc0l$i+Lg3)є,p 6 /o<4c4hZj]W%%M2rXٵ|y D;U -/}JK4vQQCSM7rUwvcwC-sq}v)5Hyߪg{3PvU#bl4r5 Bn: *Qz:ܘCUeNVPB.gd=!mžҦk]E  _`™7fUϪ-cEP -@ג +¶CJ.jzjd׌? GW<3W.ʍ]WPF@ [# ~6zxK\A5T 'xׅrȧbPJh'Fb&Y2 Re1}2m C`P%l=}e*pA{, -rY -<ޤ;@oHv%1IX$_-!fa%0(筄^puۖQ[N-'1?{4g|C;Da  ܷ#=v!/W^{T\l7ȋEPu\ QίhiHhGA*l()?j ~s{tTmya+Vl+0SMFy q?YI\V9?䝊x?R3'g@3RhWU#hj>߿ɌUK1i^ -Ds^sgй$҅E1WL[K`Nnj^$8%";^<H3ruAM.޲I[|ZJd`3|;*H߽wqf :jA6Enh2jo磭^hꎰb62CK&2:K;*JZ| hDl"3N;2("k_8q`pV2]gsM+Z& $Gnij:~=PtG^ݮfoxXD^ 3}9(]+^u-Nɽڂ֐()Tpn-/tiz)3ͱ sipNDbD~s}m<(2 lD0f| HݚT%ɲSH %c2aZPEaQy8_r )߃+By]1 Oƺ1 Ot`n\j_X &e=7l#|*Vt]rsuͱ{"qSIRg U"z{XVui-IBZjAv[6 -Ko; ߥքH:ΛO@Rdڌ\2|Z߹M+&ݎrR7Չa2J^5;I-.a2.EF}/e|gFnW0b|Sv}T(n4ß=A(:Z6e0e(nē/ߡT#hOMtֱNm#r1*ĝrAM4nijC0H2BSr*f_#M< 1e{_/utKi>˳B{"k(@ c%:qEg󘷮EdUA5^f0=e +#Է^J$HVSBVZcjHmC,JG9m)wA^AZ(!r/"~[v0Ň6ZtsS PqGH-09n;b ? ?y2,~UM?zt9d0A;ge^9ȍ雤{KO"sL{dOotJJ?SM^ι%u_cHhJĎ^0lh>TC53 Zn"ھH+nu/Y 䧚z(Ωy1.|F捸A© w3F+RMG-s<3}y)$͝nfМ }^!i mhdvEl,8R -!B24PU[tHfź,кq`W"sDt P4)+5k}&$J6CzF"^Ʈs'1_{n?AȳV |6O>#$7|7U>W G L:|]vM=>E(+zzPZL^CaRAmtQvĽGNXCdˆ>y kӂʴLЁxq6DR%֫'7EC2uΗS+]>ޭ]깷@z-R=h؛ -gKBIB:⇞RIcjOjѷ3N]3Wb\OkU0$=ؗ6vl[*N'q[mnKqGB;tm3g`!HqYMX7k(E7j'rQKI:o"SIa $N3fAՑ9M"#|eQ$`]f1Cp|~,! K@^ Bw|s)M /oz!.>֑wRZ籙`x\τ> 2FB#83ץ,~7)S[$ͲZaH0XZ bcㅚ$BLRh"S_,"U:lW 4SX^Ls -t#߻)A MtP蝐 -8U $?wf% 8BORfW4?C Kh?:k"+%'%ǟ4^ZHfYf}!8cgh*$;CьJ&Kz4Xl1ұ.Z_S_|zHd8*Isܵ)ܷE}0Bd$}v σRa xrɀ[+9 |I@H0 ?ln",>L 9 -`<3Tjj 7D sysr>E_Y&SL)ͥ#Mg y7ۉ^u.:ݯAjƈjlr-AuT] }AyCqe)s u6,R/1=5MʼŰ+Mڮo\02NoԛV*? 7 q~6pgVq{@w(>0)~?A>2 -$a^ ;dX\KJ9B}SVϻ9 ہs*ܳ@Gtb|۽V)*L<@vOBC&ӵt{ņи*p0 -בumtQ(])=10c0DYAdiѶbʌIWscM;'c!{^Xt锼-bp1/DgS2tosH)%y3΢xC2[3k_5 -@8~MgM2KaQNQP 3HNQAq$ktk? q#ȿCs s? q?-)\|.(MMNqb':p)%?[.mDTͲQ洩|}6ˆȏUq GˆFG]`ǚ̼<-ptQh wK0xB>!7>ZIJhV?a7`6t l34=0Ұ6u::F' t -&`4%|??5N( UI|>IC6> A)݌U_3U'kb]Rh{ʱQ25F-ȡ/Sk !d8.˛" BM;ieKɕKE=k!jPxՓ[zg-Қ 8Խċ;Zi Q[C|=nɜpTTJFZjbS/ JW2CHz+>ƈY&7=%g5JB^| -NZ>PR_،f-2 ')8Ҟp$|%mym]MXxk`*8N=%.Zvw6/ҏěܴMEF܄JPJ] -FTk2 i!u%Rٖ<3\xahv%w3IQgMLIl;rS%(?mk>֞e?x&\frGsaS/Ir]UHt[-|TFd'Lܣ/1t\ F\e' - 'GEYcz؆=[tL0"ix󵗶g)Ұ Rܸmm_ c<~{C۟atE~/Kd.jSϪotuznm[.y7u;ݲč}7N@^86UBI.Wzt4cr3oz3~!b >]M/5P5O P@}RVO0y[rKo82^ɯoİGѣmtZu/հnG5KzD i3LIJfI('=k7` AK͌_Hxz+&=,iKOj4݋H,oE1M>6f^íc?qXwBIp>̶O!epd_Z]VmoNld)>vF0w? AQhڑxܡ'qҩ,Re:rOSzad7'F!! އ,BFj`5!!/6#S#OP9< K0q9c -{D{0F8CVŗ\Ӻ5rwp)o+7|%$1 \DϹyxYDNk{6{ygqan #̏қCWdWDkK=GGO<,K'>l謔:X-i ~%*2;D8}l9t$UQd9ٺ#rnՆclL]#O @3Mp^CEn*x=Kfz)A.yu4̃["WpNbsB~j!*YtJB [߯qBej䪏]6>=؂He97|;?>wQwoYBN&;q˼2{H`CeWч6&0]Jvw8xԬz%`^_])uFT8dgc03IP.{ez -/q%$w =Uv5鯴DfM:sG9Q/h7 ' -_ˈtka+eiX} -78E]87]7^MR.]U>K0fyZy,onx=p+4xl#{b5f~Y|C{Bm;;)xWٵ]^d_=^U#薀_|feǣ,ÊIJ`t$&[_adbN畻ef}/㩬oOvt[uQ:smJ3QOb#!1Pi%VV0pOly!%sUr sf&L8j܍lkK+bIEC礡_Dl4% FI͡ f$ ޱe1a2J~j8}EE? U|o:v(>羓;wLZLVؽ|NɄj5oʥXv~AGU=]e 5gG"nrr7p;sNG搗t ^e>P*|VD$(?4h iOѡ,*I"[u\0 [7 E7"Ǩp?C04Ȇ5{%9J$ -)F(q~})Rxn?vI)$hIoS }aڲ愴0G$"W$ qw+ -셂f|EO^qB쨐20mTސ: 9 8qAp_ ݜ(ݬGaegq4t|R6JcdWj;%K: -dsNU2Eg3Ŗs(!sŘjz7ӲϦʆ޲%V)2^(:Rf֫q. CI|(K; wQ}+Q'I! -Jc8]D(OwMʴwX~ևr_$x*3~%p>V#ayI -p$aTXNHR#_Xބ\@@ 2XC5f͢}>ӏ ݊+nz?Cc&B297+'L_8-  -|K?[ ([,OѦub#(P59Vl _=/lhm?GΈiA@,u﫚J#VȪَ`TVVޚ']saΥak+U'OkqN:/Heh}b SHG?GuQĎS8UKf`VHWS/nd`.3Z9ў) k -7|ڡlT=Fsgso9NJPcJEv RNϮn7S2gwC Zv_ЏȽBNa3,Ʌ;h v)N8:5f \, -;_Cb8v0F ! ~\]pl+e'}ķ8};wl3$0cܧ.[&k1|UUD"{`ꏗߣE2=l=pǰ2wdDO?aQOoK8U[*N$efe禎 =ky2xhʍ tav8`p`59= ՙe>#jFs[/;iuYc{tqz2*"tt闵ڕ.,y$v'䡇M&8"@WOF+  -4'\}#} aOӊjvGf&^!&f6a^n)}*ú -;d 8(f#4#O7yd Y g -qkSDdW:oּ{A8GmW>ףsW+Şd˽gHǐMYH<7BcQaK!c.N(oE!~5-Mt`9NQ a&ӕd߭JL(~M\Oՠ$"ea2<^O&CP庳F!+ ؤLՕmj.Z4c~xQe&=|K;v4Gao_H S(E4T ݎi5TW NSI'd~qÖ 5 R8s]i}1<|c,y=2?t40eg,.2>htSM4( b\WNx"Y\p|5L>|͚J͹IZMjqhaF8Z[aZxkb J emN#*{cc ).bVѰ[Z Td=sX>A2g -C\3z;Nk'Iueq~H^U?}$+ קSi1&E1E/e8md2c]c  Jo5َ ڸPE|́SIeubRnjXK;43 IJƉÄDؚ?l ft*4x{ʴO(P+]VacCBTVihɍD2\XejWƳ;*R w5sV#Mk:X-=ܕ"]nz=ʺ\]7 hR ;VʕSBOvQJiQ˛ofL5L+~ڦ{7K*y+a qJ :SZh0f6פ?pϰ؎Z7KW)%SQBWkPyRn뵎H',.t=7{L$ƻ,lH&$Kw. -5A0X`[8h8^veKh&/ݤ3F!?xL)eM*>0(@z%C2" -FWGR@7Hqo4dQ~.>J-tfk \jS:^Wa{F'Z`$ɸ,[.:MU-S%\DprZyO?BϯBԟ?AͤU*vs R#jE37EijÜ:'Vo">ou@_&F5|hYu &Şس[:qGhҜfgV_bY3\Կnk?42N9w2<'k}5qAy ʂ28̑b15}]WPe6I'ygHXaBRJh'EIďae|."ۘ6X H7fs1nd^L~9[ϑEX\d”6 -V -U*:?T"7X~@ylNZhq`RR= `B7j׆t [lI2,"bum䍩7OC Qwk7|Ո"USl֌$}| mX,zk˕cYea":e#R Ro O]+?~irigpzF{r]60*ݙMjyGđh:fkK6Y Bm#a.` -NjZ"# -wbq])ΧԪw f ScT,8;mXBG:1'j9FTD67{xEmT?uG^by4A4AIhTp|htܥ$ ; ?!2U)#a8{\ħЭW Qvzk,he*Viik +HRs/}wEcvs#Q<#_OZ@*&XT>* 3zOx>"Xx ԑ<:l4[ Eb~8733XujCޭ[ЖaM*ӜB/K.-pCRdvϧ -@Qک؀i"ߒ\/3Ӕ1.xXdx >scNjӃ:nW3}0bu5嬴ub -z,wi %$Vm -Bĸ0yZ+ѠBB)-HcDAV)Vsr8/YVdI :|J_]]$Fr3 tbrKB1Xʈڊ$1.䜖ENC|/s YZD%ԍi/ &TՐ謐hJ b#^W_dd1XYC0饿N߶݉V Ka(wP>[plނ1 4;wӈPm~5r٨&0Fxe$-<* 2V-Ds:%г͸RNPmhg;z6>EIp`x媣J.(ќD );GqsbRzΚ`%gS)AR\dmA.ikFʈybWL~w~v9T}( qt/3t]T ?WΤPaxq"R=+GwAxO2Y{m maV|Ӳ#ovV"~0Z`qZ2pVeQy4^I=zd*c†iZȹ² PF̃2lh#!;*DqZ|Zc%[-`5V)4A#Z_7śR>NeC2*`౅CaDwc(XAh  c~ - x$>F0*ևcwʍڗ&], -ޢAř"~?y$,:Ck"л `͵yrjoGðV~p\ ڗ",(ػ'wA@ Ӥ߽UYB=|ŒsJ\UFF?,:1>Nş-FṔ-xjByB<>D"RT̯6  Ov`l6%aipĉ_Ft6ydiwW*f^t~W~H}2YeY d4!7 -žZ/6_cg{!;24x -ہ5RkY k>ED#MMՄ)S{Bws$gt>0z v+X̀v7!o+-Bo츎ՁHl~IrDwd4T'IF:H?@ n~Ġm*s$Vϧ!>f~I&x7jBX9{e%c)zFL}IY]_^Z)u ,|nG}^ *OLGlcONMn+ǤCd8C=vV>bYK!uDxvnyi[*My/ -癬6S2g@eU6$d"J HDEMl{%lkɶ9H+:c .TICdInͲK}D -M෨K=JG-CW.P)H* -gh`^`hLNL8L5n:jyAn"sv6X& qWu˕. -endstream -endobj -339 0 obj -<< -/Length 19807 -/Length1 2244 -/Length2 18442 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴweX\˶-$Hp4.ݡqhwH.݃'>Ϲ͘cʨYj QR67J؃ L<9y{;c34ĆLA! -CAb b P4,LLI ~%&y X 6 (;AM^i -y wp[YXB`O@ -` 203]_Vj{hilk7*IEu%UĪ".IVP5jA- -j:Մմę p/mK{ 5U@m 8022X8;A Srڃm`-8^ +MYAN?A"^[j[k# rGKcb唔vV d 2}uCF^fT:j/w.b2=[Ooc1c?z69Y9A0QgϬ@%U^D/o |br<.&37uHAfvvOO{;k+ͭ@f:ortJ o`@7SK??f?6x{:;̍mVdO'c vz{O 02aA+4/g:4d0#3*C^9Zζ -v@nYٺhVg$a4SZ0gfc`b`Xپd -s:6 / -ڎd4^_n S{3+` #3 ;;ùnM dy 88C`?`c0FFQo`T)q1F,FkNտQoLoZAoZA߈UoioZ*|k5?bW!J̯uAK?W!  Un8VX 8?8뽒NVn_3@_C, ʅ#Uߐ5o=Kczb{VmVfCVnLߘwgcгpV.?=dX=^??t"/ٛY'օLf8*В]Lh!I -oK̵kQa>5ƕ_) m`PH_FJ/]6DP>ni2~@i% 5olᶀJ0 - yÌ2^6wl 'I i= vws{Z]YޤJM<&.+'C>R$k(pX6MhԴ5w?Lȕg8VDNL@a -z_]Ϗ,̘^G͏n~h1Iu3T}l_R4C2FCJj^T@sNѢgoo}*вk.]$CViRʡt-csG*αb"vr#DQ%ςHK$[s ޶}2ңG "k%TJ3La3/5jj 37C8-1Gʼnw· -y͗<^G:/C {˺3R.$ vrݬ’z'zi%t&_طP]o59YX叙~ʻpNQ ìp zq,00S{)PjRFsaI.KAIKyڧe#`1eplNHݎII\s+ʚuA&]ʆ5ZqSOb! Ĝe S\U]eRuL}JZ3B!ΔCRɱhIӎ)'~-Cu'BKsf El۝%%E4fNcV,ˆ,zڶd.W\0 &P/^| ώN,!~Mva4y]m GZPUzn%(Kh0>+<[ͯL (I>9?C+qsbOKv^TFUY1_$,mc&Ge!(A?Ya7$ᖔ8蓙ucq)\In{D@mu}WmaT1}:JZgwedʪSaV{:" QԽ?ƶ!!96K"]YBwxGÍhH]{ -O pՐJ!8b 4qmCBY2U1)o\nɽ#/>T|W)TLt h]ʝx9xF6nK;ϛ24L"u1#:,l{~P(x2 [R'Z?=Z! W'IQ._U[_V@_sy*!Cj3NȶnT5#_NU!EOV(ѫ\kw`zK5PʢqW-D1[i~QJ^l'nT~a:C p2<P=[{:3j:k zeQ/N.krtL=;)7nCf+I03r9Eqrh Rt@G,4椷 4MQTkF8(e%6fp1J"g(o 5 ^ ee9s4SDy%D= '{>6=1kNEH^>kJt a ?ɉd6FM0//]p{uDl[YUUWA~BkI a폻j -06w@ޮ_Fyn[Re/`οyw'Nc_2/~Od 3> -m)N@ePMr^L~kҪ:v3gLZ/D#NC " -JI餭]əKYËMJPyHTUX*E`+CC;۾(, -|2(МN[ezt/k -7;CTYo -Y"`7'f/Կ ͠cTW-'?T% V9Џ֍eeȱ#C'#q R9 +]I -ȸ@Kꄜ+t,ROI U9ً~j[ܐ'Jw#sɑ~.34&(P FL,l۸z_܍s+xx*"w,p[|G'^h)C!(F#Wx(jV1vzȩ>]Ts|̷0+h"Кz>:BFpk3N¤ՃM-N2z" jM7珕I2c()7OBf+7 -gE]V_~=S} 0kKB }cNhkt[rxF}rz_.|FL'w=ja^s\Kfs4~=_UsxJ ňm![ԍ*t2 ]Kh98FgRrբw\"_:щ&ϕjc/w%M+rϮ\P3=JJ8_# ҕ"*x3- Ȃa? |)☀&TݫU:Ök=9KG|#|sG"etH 4*1f*!ՏhgaډCfoL a$Q}a4hLEL;o=7a#oܢ~,erğ|Pl|$"a͡ m[Ch j Rx*=RWMsTs <,[@zE^BV9F=pyg,7bҶCL -4JMOߛsN bIvcqo9؄}76;Dæ嫟$LJXqI({kuML#GUxHfKvaY\h~@,y='lRCcEC7af!nHnªRM]ZMx9Ɋլ_ v CwDŝ*P-h;qnV" .tKgTT(/k?1p%./Uz+ 09̷ex!lN,wϣ$Ԯ?j$b"CBs]q_N:&!ՍD@6!eY|j¢uyjj"<-\Nj:)>\;&ٸ4 `m%Yߴ@)YvZ㲽S/ULxq[7,gw=V`UT.xT: ^a>־6 Ecl$|m!ESPHyē]8Ο%]9:UM]s.~A07RXf~_NiyK2^**^#Q(ͬg'.eć$Y37: --:=d^:ahk)HxDz+]vܽH -(>W2l˺yBtjtr)& "@)H(YR*2+Ga%\!N઒Ϭfiyot_VӠȝ6'<="JQ7hS[??/&C)ӊXG dpڕ]|ĭv= yW XyJ -m̓C[i},E12\y#]S\ GViEXNwM3: ϸewM,7𿳙:e՝(4iK^꣺*O7Փn]&v.p+6r/Po'Z}a߃_"RMk^K΄ktܭg Kެ닍Yrs"J'nͳ̡L0 4# -݅f -k[tw>8*5O^rB%%"eUˀHJ8 =&A71XvtIQ7zIe^*(qEuSϊ%Es¢8}6<*Lˇ3LӅT~`j}?{'>1DinmhM&|EA࣪ύg~:GbRF6"D1} -I禧FmBIEaWَ$0ssV/v߼w)'YMF\[ajQ?Y3;ݔfu+ ]/*YS.sD"pEMZ'q":%3, 7 ͰPC:4(n*qzGrkc4vy5LzJW -~k'pg"mVfq6Qg]=d|9zWqIzCzI )Se`{K]=" -ňTq-qͻ7V~B\iuv'Yy?$ (Ξĩ&. R./_7|^d7 W1( NS< f"!P -AnN@,}S9 7~En#\(/j>w|=E%:vKX۰LoOS& Gf|p2SHDR$=C`(3cnm鲪iH՝C )dxͿ_ |bѡt8warJ[0b:AZi] SS5gD3Q0SH}Q~ljk#C%oS!L0k5D ~>o2r(ChWiB3tqKJ<l;Wm~)]M./~ܵ¹\1o_Ƒ zOsi,(5&nKDyۊ ʺ Ã[# |Dh%Te'G8ޚ3NGtSJq ;bκ8[#%&@=-Wn=Q0DB`|%U\%*0E38#ˊ( -3|?kI~z0-nH d V Ѭiw+Ƽe>1ӣxK ->p?*{3q³g^> z+Z:C6ܵԂu j:g$ bi2$ -[n )sP1#ހk)/ڍi6d'lr>s~?qgU;ĤMd"aEn_LBo;[cF #v (M探 24Pm -Z"@l3w6[ tv0KdXxܷM%i$9@p\2~zHb] fͣӔDs2F~skNP8=}2ݐx - h{H%DlIlr̼@]L4g]op`t( S63J/kpIZt[B%Z.UHs^t-8A9Fv"bIb0Gq􃨑? 0<UjH ~M3>4.R/#iDO~&^ocL]Z +GRMϝ\" %V %tX/c\=!O$nn(uW`]A;v6_XcO+Bfc+s?-,>lޅ%Ǎ=A7q ȳ`<Ƚ*N!l=@o;:}.8q  T_igt =7LGiȜ@D8Say.c%5:zyQ X¿="+1ca˦!b|1e#4ܡQN-]Z@-RjNAf ޥ'Lo|r݇6Nݛu?PD y6)x5P/p%PbI 0ʥy^oN{zu0v=T׷,ZA&"f5lCTE]+-jX8W/yz ҝ ԱݑL|$%r ߧF)=2nϏYT/=? -q bH>{F̑rqO&i`E%? 8氲#}Ssz)G}ނzQ$I -i×gQUxLEGhKբHlWA~w!7`:uM!h VYTcTK+.J#jsoMOvEv[ism"orq{,N/~Bh'n -6׍'9ؠztmͫ <ߙ.ui⨌TQϽ|tq8PP ;'C R"? 8*ypdC1#: y7d1Qh~˦u'n/=-V 6g(d;;OAWMpJ'q grW6 [KѸDa^2p\3gJɀ%2I|~/R(\Dg&--cA[Hʵ42ߕ623W mnS·)l3Z9f[p:}jۋs -nSݶi >H/?(M7kޅ7,=-U%#?խ8ckd#ܽ1N g7Xps;Dہ\_BTdl=?B+Vʂי.pbgӊBOǕ"!N]c9X+ґx!. eCeP{s -:۹3{4ltфҥnY/Fx2ŸP晾ƅSnNqD|qaݽUG~tzL:[so7fFTLh[8ԠYk6UYV+\9Y XDd83V\(]](]zJͨh;е!Ǧ[ -7}1"[>rؑx07Cء. ?. 'elӕDtOaՀNﭒcgC)wQƏ+OGJ9zwdWusşjLV#buQH[CcSst18lۣ8֑g2Mw31?ӭX}›4 kwsGbaw$'iY¬L?r:tNkgWrKVLem4;5&~d&m8v<;)ƼrI 2U=ëvpoX~uwFyu7Ҵs3od8O0zTa%9]T+}$AӶZ F J7En]fN?x ~12-c c2KБFWf#~WAM;h1gePܕs 4 i܂A3Y;Fl1퇓)3Lӛ-i_[h1'ENx mVJCR1u&#uE~&\ kX8,1|3_*Ϩ#Mh+7"Zob[ǔϩQ7)7%p߾G|ȂK6o )]_IhIޜP,x-n\ 0};<rIT6ѥ|NFTƩ,{i>}! W/ʼn/s k3!_b wsÀ.dvrO1At|xqRƻz7TҊ=zy:~9 DFH=>Ip<QAEM<vGE%y„r\8j$F捩4ۗũDj1e}<|KnL?wCD 0~}憋N5HI͈ u*v!uyP(( &G44c]rGacʼnǔq -CFUP[D4}Go#5Ϯ[63J AnӘs*s׋F]w9?djV.)X&1,J6jּi}ƣN7eN.ڜOٯ"1>[^n0#:,` <'.sWV5'l2b^2=l6!1( \ܺZdM|=8خGޟէ79n ;H&ej^uG˰)[ x yc9ܜsnz ܹ]1hF-=W,hXt}zÙ cY/@Pa^{e-S\w+ w -68#{efճ0/0JpU:ֻ`D)0VkBSw`Af ɇh~ɈXN<-pse_ԁQeL*FMWVNYy1+5ҽb`2v AS0K6E ^fxBqr'$x:f@bRi~f$>cK8|7CQ?Ynxc ,΢eniOjWnuѠMWHaK꾜bx{ )9 -ycʶV@Y5r)~[f>WB|taSIXt:D7t|[UQa} -<]L sl@  Y|!T<'_mG؅!]]B/; | -BPh!⌋{Ur#gU O\(j2x%b[<}5ϮO<.8*:>ZLkCl^L,VQKEȳg6 q[v.'搚ڻof!^t!EYC,Injȫ]9C黔R=7!y2jru,}G -a}eRLhȊz@%,Gs~'T4tn®ͦO xW/oꯛr7xn՟ZzճcJaR~'PVJ|SZ$r|w=/r[84&r[:wWvw_OKoO)s~jܬa+-eKKեTCIE$O1kh/oNZQFDɥڈ2VM<h"ıB -bX762384pPf&0-bHswHOc-/--U (&Iq\Bs{ڳBwrK;ȯn,G򉮎7kX0Pޞ>#+؝u[R`hiy1CH_=_tD -2`=66mk)[_ζ PיL 푔pXZQB[d -JCޜUX |qί;C2] Sms^1s}w"_pj=glSA7YCKsv#&7r}eP76ߴd)zzQHVlL=gavG r5GUx!L˔ }2_qƫDu?2 -(2W{ZdD%య$HN3 -퐙NC䨈9ĥ`T4 -i\2tli mUp5Z'첗e9 EQٺe>SʴW#%) ƥfټ⭭TY۲TČ{·9i- :m_ '= -QkFOŢC w2i#m8# P㰄I'pX{o  jFdI4LC\?xEȝ;6a[)!g IxJ6 eU \,ofp]\rTbkAʺ4p˧.Cp+K[!\s궪sT1uș.MqDY6vF"*h?14SojR&S,hX`#ZzD* - T~_pd+96j=i >HYw+ -{7m \塕k!Z>e齦oԈ)4kVOP_UZ$p"4ѠlW +A -QppC捅~#bw0 rZ&ӻ '#OS|Bs-O6sݐPSB]\_I$upXRlD !(KdFVI)קM~3@hB-hH0o<)?{S {YM݂ynL]tp'mdwfjFlo5,{;݄VxFgI% (TY0#"V;ft2j~ -'qp3sݒQr-~t=+ Gh4GURK%5o<.m-0o9RVG/vkxv* -VO R/Kívy+w6?A*L@av,Uv޽T63+sp"]B_z,4H+Rx$Tj@?81}?h`RnetnOl@c-ijwH:쪩J1v{X鹠O6aYFx̉BCH:vV]z,aCqN~jXE==mMk7Wt-^{{XE4🹱> kz܈2hjsX"3*Pg %>e9<S'%j5FN@H}p -?X224C2nd%oB.9 #`ۭ#Ő)gz2|: Ϻڊv]}'$\'7*3"Rխ\)S,].8C.1_-IcIJo4g\MIc;36NXKNJY\N ެ\=&Q)lI"<[C!GӪ|.?-k_7aVhwy3N*Ew +RL )UO) 8MK)7Ĝ7ќh.kG<v m G9KNĻ =EL'HN|YBwC6Fj :F!|(y1>A#`b=q4AFVf.=~ZMM`-yi*hUt(Z(/w=]H -2B)/z -M%/foE!fcWx2$MQuwآ -TPj:wa|&<e u{Q s# =J&e>nG d:7zփ 1kjA\!Y^-5X;!hSaCKnuh], ?qPQ(Cq2'i1ͦږ,~Iy~?fl+Fe%lҲ2%ݍz6JZ},7Owr=73򁬽U^efj,3ouh8#לNNU\tp]|(57p6T=smg`Vta]%a2u~-HI|iLiʜz6UOaILS@X5SɇEE_+n4 3OqQ( ,2z2>,+w"Ck7:AX?u3F=V%P͹pV|B ʡ AJʋ^+ SQӠg'ML/OTG7^̩xCFgM)/Y4!8׻ΚnX ~=@.w˾='=|(TRq[j-)i9<GnnZ2 O~>Rr}"4?K+0*a3H |CPI{ΕwwY,Z,x([vh#Sk?=&BH0Cy,a}/ǣ䧫>,yYP6Rq}T3{~^H @g$Z~sp& -i=WuJg":NЄ]h8ذ`nj6Ï|xY-ifC.. QWclcw' M^WNeiD6vm_5m -d{^ 5]A2u$jf嶤 -|~T4$T"pٞ/!;rA^,9e0 Pfq7gaˢίUK% oJ0]hKg3E&?" -r)`|!߂-Ј\VR{35F4`_?fX[Oxjc)܏ޯbz( 804Q\? LD<|M\[`d w,ފ^&;RLB$;l:#`K/[F@,y vsWes5f6\ aGF xP?uuJުivz{) ßnh #T_x> q.pՙ ٴʗ&"L?")5ucZkr;kLz siFsKLZi $<@JvBl cIƅwCݼA; #*eY񨍋~߸^>sO ^hgCG|g.*ɄuE$,m5S6۾nYv\tXg^%xnEM'r)D|m.+P;jZ(|i\s`R~5i3n1UZ[Td!hec e̛ 54U7u;v%AgmFql&6({ * zF_H?J?RSr)qO >؆M.b[ z1pT7znǏ/gJ>k`ihV8 &P8s{mt Dh*Ϛzqp xtTfP|AP7*/tnycoc+zUFrLsr2F?]( ƾpC1d* k[,+\Į*uIײQ;_!.l 5Yn-nJzTm7Yѐtw#xGFˎzni4͔C"(Rg7XI'nF]*;SDkC_YWsh_jf312P qѡSg@w'Dz WaDosծu@c|@I6IEԸM}NOQP sXNϨni& -$)ʹ GA>~J_@7ZRE,c]TΆ[Idw Y+VQ6>c_X>C`X5 pӧt2[S" ,=,(gk"tY0\ߦx7U'< =ga1=3+R6ɂ^05ވmŔRZE"V6'ѶXya!G'gC ُ'^*Տg2Z[*R܏ ڏl<,0ÓA5D /R\V,8;QWD|K%\|8_&$_}˾o&q`zAMj~qoGGa:96E{7J"tNO/Ӕy)aSf u{{&3-Vcoc"T Lrz#%CVgAWx,3|0^\xQfv4ytkóYzo yMzF:0;'{;kT4;lh7jğ_y!oܲ$D'kV5Y@x5Ws^f;4m6T=U~BHc^ߠ :e ,@d\c/h u:G2]]z޿I@3+Qt$uubP%H!$U'.GzS6F +Udl)'s,ynWW@IGp9v@; WS&bo#"\[[ -ov;z%yu/nِH,֑ktB9 tF:LD ENGagZdTæ}Pr?rOܕ7An՟>GT_~Yʻx얇p )9ZMh€j#?c@u=ؑIԳ|Hpj/58` W"߾Njo:.?EU}^^w^ٻ3lJ>$ueţdzUlxMMZS:MhoDpuP$BaZtFF`(B0v!.0yz0 -endstream -endobj -340 0 obj -<< -/Length 17974 -/Length1 1669 -/Length2 16869 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴeTM5L[);.ww)Nqww- u?z?oe%g={̊ -B&`#8Ƒ #6aW9YXE쁆 #h7v|O}`b'HmN@hf dP3P;8:6f  {df+=JRƖ`K A* hnhe -TUe1%e25{ae'[[`QVQ -ɩjt Ue*@wft9w>eTT4Ę p;7l =lO-# #ތ|*  ~Z!NGs -] h$w{'oM\!hchch0P  doBLWpm q98:"` -Ew@6d$ŔUeޅgC/ ~gdžDex\Lfn6ӻHlLD' -zltmivvS_MlUm@vN@I~7 `@WcsƿQ_3_; ^`[ d -|{8:N@/#xfN ]6`HgRRh -(v|?z;YYZ3YPJ%67A -4Q9% ߵ/dcf|_Lջnߣ @owE[,\,w0*(+&fc 6٘X9nL:`agx0KP 6`lw1ٸ`_?cv& l\Fw \@u?Bd ALa~?j@ߒ`Wzv=++ruwjsw@W1<ؘ7"1[,o_C*r)u G4{(FƣϦP<u%bⷉ7>Hoi TVft؟Ցc·H7Dҟ9P.MV]Q'?8=`D7Z1 D@9j7]n}YF;̡ ٗR3 e0Uz-04ecRK^+𿻐S]cO->f~.bqrBM5frDpka -Li52 LZQ7d,琠`you YLWC -É+pLd{9Y D[K$~ϻ<"Cbs>8_%@כWL-GIjɔP;'@>.4HkG@3o=O1,zp!GiB3>ߖufEP<4RO1NTOLE+B~&+9;duGS P ?B-9#5ؓEBLf0p=+l5)^Nnqݶ[ipSҨxH5tEɿ:qQ{sfa1rl5'ŇCch cʊN9Qq9i+zTg.}p,m9g+%KItGܡݙUaQ A/zyNmOd컍>Dp4 \u<?¶.*ĞuqKݑ.<G%!qQۀg!<ce9Au8Ck2-2+[oxԆmM?-_ U=>@~lMWozrjxTt]T ȩUQo<6hc/i8ZPU{c UEw-7kR76>(}IU@yQs ◵I^,w5Û{ 8(݆ߍcDӟQ)aN_[js"6IԎuhd}٫ SBzvGvH1RX>9fAkaJ踀t;"o!O[Q靖)L[ [åzossKA:CH $ZYdXTvj\_ -}|#Zݲ{,Z|Uކ~S㗔9,L!ܩrBf0ZRDE ޑgSo@f-q:e)gK||UUhMbx'G}q<^bMfoZ5̪R}Ɏ҅mJc3"r+.7Ot1‡mt;ibd} #bZ_8?1zV -%..ͶHfKt -?JUS\ ׳囒{E!V+f)23)g H5|W^`,/_xpf3E$c~8 4=&ȫ ]4nTԻ2kVo47Yj7`{yO5^RWzE~kYmʊ[Mh -Wʩ'>"RUbލ4TNwRP%-ZOȷs* +i+JhR)T_p3 ?xhw=)/u@7#VO*ejtI ,)zcxh6fM-C,t4k;g16l~Z|lHgؽcNu}̞"2._.b`HT S qY VXv_ gge`}>e7 jNe~,}`"*^/v+gyͨi,˧3d TVA<\Y)0B+n`1y5lcKϘ>bSßcV v/G*~Cʣ2;pVҘ:Ÿ<|G&8UeW| UUE|;+pPn? quh|4f4pP_;^j;v8{X>[U |`etyA?b!,04-8GvZizTpC 9"Z}1bh (@iy*7XBrPR5mp2׋+3ǖzKБ9zj֏/E^"RpKļb ]ͤ?$~&VclGBC1)#ְ4澈,IA"C_ɟ= '>j2Vʰs*s} &;/ōNҜ̹'NRjrZwשq*{mgKhbawq㕧4=Εgur4 iXĕ?+ q_wOnZ㑎{7+ v={ޙEcͨƿcud9cf}!/Hg>]QTX{n=3-WK PcVtɰc8kKo܀GM ãѼ#]n異~ $ݰkʲ:82*vx)e`[k[Qj!ѫ\,܊P1s/^.:UA>u%\kJ8| 3V @s@5(q>,-$_[ls"p}XGRKooN8h -2ȼ]ɒccn!ռ½2[2L4fq/cj_Y~`pbpSX -|4VtH8UGB]Ј+ǝݹY|b ^DTLɋvs>8qO1\1+o_:qbev4Sq M>>gZ.' - h.4cbJk[%gǮPb;`AGK7-;l pKu`ZMQ@7k'P(J-+'"7!-+ X:1"Tm-ա+Ƈ2ZI7j}PG}f񆁾oS6ұd`& ,VY2;ED+ޮ -^v+Z R! R_f-ί /Di~bo7FloK 9s\a]V=aJG)7 {8W=[` .?ӿX8NyJFA5ƯvFoXe{wq.6Iⅵz) Fȑ3ORJwXBk7%1}DaLg( -Hd!UY!wdQ4lC do(CI UOqw~@4M 'ɫ^}{q~+s|g,qt´ɫU5@_MC-S <ހpzb#Mނ^ gTg{ "975{j;gYXE;^:+E3Zs$#;H5*ʙےu܏{><3Z-8Z*ȕ )󘚾~ >@)[;͜ /8Ĥ ioET~TDiZsKHȍ>OB`P, J[ģ.!%;E ‹%LrgyVi7;Q7`:yC#naB'KSg0fm&$ -a$&5K^Fjp[I٘z@竢-pg@=11z:1b@wZAYM-QLl~8kqJh$ej -r#ƇP?:^X}J( J"DI'+te0FVAMr'$6+U"3h GpnF :_x) VT+C Μ(3rkBn]fYC+З4 ,`b#XCcS^{ř*A>t釮Ƥ}(fuD5]iTZa.>2 -sBjzMcG RɲQ%kI$$+Rs*ȏ~T1f:Kb -~\y9Be$ٳքRRVȏNhLTØ=oZE?eir˷dM;k0G_%zxo0Y_F#hPe~|K&Kqӆi^(-t^&ԓIf"?'!o8x`T\7M)pP_&x3'v5FRhjlil\*S [%y 6e߆u(Őew_iIؘje8ˢe]$;Z R"TPʬjU*ZZ䵴al4\üCPL61ɟ^E/"_eK2?6 q`k?\P7iaYՓ.ۚY;cSB!&PE}W0kwxnP/c'r2?Xzț!& zX͚Ebfh TuI=LCrpjq8~N:s8Y@bS;QD⡿kЂ:($.| i#A|OCH}e䛓~ -JAr?-򆞰ReuœED;v/Ӈ#us@n؀"79'|I4+Br]w[T g[ -J$_?%j,eRNN ]){(h{biWs`]);vsX.M7XY7cFA -n0E-ĕUAíP,56EpEDJCFw+&Ki ^G|ؔDUuRYB핋H^F…& w$)) BݾȢz/x@BMSղE,֣|U>5 v\t Sعp1%.9@qM첅ڤ}SR0(:8߄h)}'I$"'p- )>vY ŦjffP|ĎmAM&"gBWs+S)@7Eej9~Ņ4u^io[Wף@TEX -O5]Gf.U(Mwpyk'1UXDϾW@ǭ"鼇欢e԰4c3}:ݤQ`"‚+p1f/3W|C''WHJ͎AJmyG Q c͊AGBCFl%V3j8Z#ӜČ7_z -mĆژn̈=RȲƠ7Sz%bsa:k)ɝ5Qjݤz*;oָcCaW1# Fwb'YG^jB7Cʍ;m)I픑i3KH3: ҍjY9o -tnkFV!=؜' :?ݚMT˻3zڽٰ"W54x<goj6C$+ԣTV(KSW$ČHH'CzrZs|T5 d}_C#ufhg<޳A},Q8H'%uzmc>*f)];"1&6 z]Ɲ/R߼ߥ;йL,KJSH'Q>HMU—!-S*<;ej 4G7s4SG`oS':VjkmK|R-^AfQX>^L^h"_*\ӈ50Ս e6֋;a gha?!j7x5읩 cfJiNvqznMP-Z5k;U r鷮X\g:Ev\4rj. y,rk6O}'u_*Ȏyg=:,@/PG2O9|S-o-O9,ZMbho)`gABL&\R'b  2xۜ(L#Jx7&q~RjHߡ<$\=)aT'uGʱmBp_t1ͬg.+? *JάJĝ32 -L%C](*r4=hIL R;-|2FzC؞&HBu)3Z`Ft4%0E -0?>h٥b_+ I\/Bz2DK#!]qx/ၣҶw\' Xb1(AK vT؎Z&Ep>~B<&J_{r+ ^M޾ך+4dd-? <ds$rEX_̒ @9‡qhc;I sA;55b"Tf5 -l`p5#Yy6V8I[L?چco>MxK7 >sOQ݌`|$q>Ǘkq#~e;f"n,w<(ƣq/>~#޼v_#$R ADdlme>{ -":MW8RP>13G kPDVAȿ,SUӛFDR,ɽ{NqfK4OTH_ļuPܱ("BݛFm215Wď~rswl_xuHZԁ뉋cϦ>78jFvxnLL7~Fg[*ψOrfCl.go'^4G#L֔ngk%>+IS.*Mc"-ޏRw'uLm!*}}&jGuX;gP=s*74; ~uRH{E -h>0*D1d{^F|-OۡJG; R: UH%?bneՙ -UbEQGO mgy X`Oֲ({", D['ʞw\F,HlHZQC&OnpN,@2oCA5vP*ja %y"$'J ux2~玪ߥ05NM3m(Fz`zAC֑ЄILIPohI$'NhIYJV^H./üp kytԨe-QDG+* Nj!M72'm -ֲlgsCk{/EgB|WP<4$WУ.}~SKVmW^_im{v67齶nss8aK,-,'b-7IMvWEut䣮s+nAq.Ͼ|3jmΘ5–2t*sÐ{+$'+y -4rdžw$#_.H¢9-2T^&Ryő9`Cx$vG4SL4=U*[ : -綻QIcB(K\ܬAH)Eoءoa| v^Ƒ;!40T~2x|Yc[tDd[js@JGU+yr݋wخ1dj X^('0~Z,v'wo҂U0}YX{ͧ}:175(z#ٍ'Socse@ HuMoU:p0Yú!c^NFwIH -|ۡrc4;X Vل1k] {lkWy+JɑuαNtiQ b!۪J<;HQCf1l"Zj!96! -͝X曄 CC;YٿfFxUC7Hd0)o&*n$`҅V?lڕp z<`+G`v#<6@iOH=FJY6VEa(\$UdG/&ySB p MV^;걹J|c1H:4pbzSm3&MtNCQ~kixOiZ,^QҧpiȠ -AlҒm*{2٨pT?yF^(Þhmbp؍QYq i uȆCklQ߄?\^&bmg k{4bs MvG{/z$+1)[s7a"@Z__(W~i"% -"g>& _$zDêH'{t9fx<ڷ[l$7meZ2,Zjtl -$fw|Yf!% mګ=]J -wX&U|=$'IQLJۙK]J>?eO:ʪ{1`sO6668GX?g;,:A/co?1g0؞>bh)<qD񦡜OpOz-2Ҿj>S1셧󯋯C\$LNO&6s7VeorCд)$Aw!C+C*0e9rt΢`鴖E>~@zVlvxSvB/ԹtOˎ չQoV%=+9Lmdُ[筽^YЦpynq¢ -JWjN#`N- -B%l!Ć#z`bC2-ɉJh/#H>eoǻ)IJw}.۝ND%E%(M,~p'ô4Դm.۹m]E-MAa)k2韡?3%l8>;^;x s)|1;\0<.6,fQC 4)5< @,n2.k_}J+dkTdi4Jdg{$&CFXl-:yQMU=vuB2ܽrEL`/hv.Pˇ.*2ϫ-6bm%NV ?oR+:P!;\V -jJ}'VvBSsoKQU FIAz,}]Odto"OFDz _wrN5E5sõr 6tFN&BLy:< VbR b=Yc3ׇMnYKTG-gq|)YBF$kA+fMW*Er&|}̘=$IBєV"3QZMÈR8 uɸZ 0$+蛅#6ܹ"*LB~65KdqnH9{eK,@" \bءLx@:1x!Oハ>zdbfa8Jn}8?z=x)~k.ٻ:e(|1P|S9xj̽ :paWZ2'psP0ѪXY ՄW"Yؐ)C&js}ODZ$A;9ng*)FRoҎ -Nh9Hj2-5b#i [PJ!t8ӯx CNX4:\˙8rqh9"YU#)lm̞Xќƶӹ0e O2)L`8Ҫ";wp`L43%$} &cՕu(Q|UG"$: W8T8RZWd1sV4ܫԽk#b~q1e8sD -JX`1fWv97z$2ϡ:3Ps~]9/NdOLvoH䜸h(q 4dI|~yusQnj:~QoXt1գ+VO4Q^6/f+ ,F 'E9l inb՘/hڟ ,!XW,GdiEWPECJ?,<}"4 _ab#tŀCwiXP7Ɋ5eK޶! uZ7ecG#h-j]L4@Psye(Dlu1STY)rX⤜ٵP9 BfS&LEI ZV@Ƅvs/-jun[ZiWӴzK&CljC!snntSO= ?ڞydKO Zg&[2?LL8AxUX_S /l?n+10z>uj=l<] -U/L˱uk"oin gژt;88ޒ_?i ($θgTMbm->al$ ˭ ߈w4SM:'Fp[Oi]Qv)X y2TtLJ DK/^3#3ۗsףbdV6S'rUS rWzݯ'|f~%Ra'ϚbՌzyMId=J}"z"*I;MI64ٕL d)DK~ұd7uPDԋY_ Ff~3As_ CxԺkȏ瞣G+Cd7J?u ZmQks^qkPng`=њB%l.17D ʾr^Vdp!7 I#](22$/&3_X1`9F$B~wy"ܫb1ʒJK9~ka צ2=?\C?K[.>}C`\E)V)Q[\l/HyT ,N~tU LDghH!SbF¢SA'jmNr*pBz8'$3Lju"dLB[bM:7ri1[{ÐNfU<{u4ݟM~GJBa-?\6cuFV@.Htl[҇z7yCJC2}}0fONl/>|k|Zڟ9 ٦*Ve|hPZ8WKQKe0% Wg]gtJ|Ht,;' uT{aJѭ-"Eg4\gK*Ne -.r Zv,*:i;ڟ4ppT  K2d -7Ë`\izKelj=~#8f|d*^*8*H6v/GNbovsReGקdlH5/ɡm " oUSf"otjP~mOt!`9"DIF l PES{aѢSITCP\nnVᛕILFY!Hv*'a,jaVG 7hhHsWT•jkx SIw/k_E,tjr;ne*?N#e퍑@NeQ{@ipqMZ_@l%7){`Rٜ'r% KNDCϷkm|)Z;H -o -xP?u@/pM߸Oj>p|Y~P-!!<%Kߨ/kkvaWͣ  -pa.էWTkTv+?LƨZN[{;tݽNee5%C<O$B?Ö("OϏ %{x dH$1ZpsY`sqJ[!yCޓ$\ոزUfHqO 8}®;9Ȑ/N)!goI 2Zc"EK3%1b5.u -e{cpͮNG8";uf/%zyΨfoygXZ=oBQgꉵYF9:=-;69 BV *Q5yW@,ׂ%XV[U N4&knRmDQf@.SgQNfUQu}cĨߛ*L`aZ,$Ƀ, Mˉ7nXR-6yr]jn|VukU#=#tDWVebqwE`N$9>.{rZDޠVRw.aN͗}B"6UHP[cq<kðgg&_r?;:Jv/J`e\q2D|V1Z"HE+AQxrw3xE)&uRWz}j_YQf jy t餐erʮ?ۣ/h6[Ҵ KJCۨAqfTSJ4(^X8& oSj#T]/Fr5DP/R h`:c<&a]o҉·kˏODWJ=V)܆})2خ*#(bTzG% @ր_ݺi#Jx -*Yֆ0/Fk{kb/?z KF.eWgYTmvժ Q躚 -EX|S= 9}.[⤅\[ޖn9RYh>VEG+`U.y%|u =X:sݰGչ)1*ʅ^OğzC;Hq i\G~,}ꗭ"QdJrP6~XtxJ`a~` #k иtk<- )J%*w=?wө?X349H6ٔj",A F[MoUt_+92S§WqPk"/_~tLo5PRJT.H>7 %[/X#Ti\ M9uJ}BtஓE1EFZRopO-8ư\uԱ> $)ΊYb=Dؓqa5 1,$D)PD'[co#'p[BdF25gH>7kIV[z0{mԑLB?tؚ]<>Xts.E8=BWUf cKlm%ա@=̩=XVo*~}fE#mX2?.z=5r_c~)rN>hDI"V^W&ŶmEDDlO2 jUO/PL=_/wO}TXfr`>]Nҍc2񗠠G ۚn1IaWGxya /Iץ[ifG^<Wx{Vp8#Y1b2 4 sZtƧE;r[q%JADvq.Ӕ[;.<J(WfnhQ*ɸFoBG>]`=hØMdu}چLQ;hON_dvϧ -@Qک؁qV#ElT[LjгWiT 1#w31Ezz{b$aNdqSsͰ>&q˱l;n`۴|xy^7+dž-[[ozQ~L\Eڳ_s`~&Hc,D吂*vzf5@&ފV aa }}=jZiJg{+׋}5W-]RC7ATv32E@'R*(gV>efE|J#)O= - 2|ܜi׏u,.GfG\@N:gFEW *HbE nx_-di;^QZXM! .^r,a|Qoi>vc*= - b2CGhމEJ'HJHr.rUm5/uVZ88?TIEk8c"x.K$ဤ|7aBxaz>r7FIf|En\k{V!?&6ՕlQz0רVk\ -ܡP`3|9+v*z"`L(6"GY1tB\! N[G) cĖa㥴 V>Uwm@^l2K٘>]DKRρJmC㚎YI #I7?6T֦čJC=C^%\(j[ѭ "H8u_R -[H+]g^?/'mIx~N, =;Rc Gg9FpTP ;G Wj̚꿸hnzzpMQ5){zҔ=K?B:gΌoJY ܹ˹cW;2 ZaQGgLޝ}O7#}# ZKK;ν156K9z ]XHW52:J賔þ.]~dR)R -endstream -endobj -341 0 obj -<< -/Length 7142 -/Length1 1313 -/Length2 6242 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڍuT6!)R #t780 13Cݥ HwtH !9=Yku3:|%T G AM}YMA  `hvbP$ -!608b Aq%" hp(]䁄آA=8⼿P$ - hѶP} -pss;Hi.^ m ЃHW(d4~bv- Cav#k - -G]!P$v@ - 9A~E[Y!p` s4h^;`W0ly :$ _W>F`jEsfE8DQĿS!V}3\{8 X*$`9@U`M@Q [ - |] `}]f !B]4` ~mZu?0^Oד ǿG,DXIOr!^>k - u ''U[#e{ݦ3v#?'ZPunZ] ;'_,;#%~οs E7׾jB!0+ qW`(%;C[vB@~mD=Rx ^[~8}prAH_#]Ez+$zI~OϿ7 -uZM#UV2m ?`0z5ls9'#HH XB&yhk 9NEF (MHޮl;&"F>Gޗޏq;س]$trOݺk/ OonVQ<ia_8ɞm>EwDMŝ|d*sE-g/R8dY(ls@IFoBDŽ{L58%F;UgֽeJ2WȝKv-~ܗjM0: X٪}M*RF?bȤ-uSOS*6Œ֣b!?۹r~\8y>ϗ1 S @Lq;ˑxe֔|@oRd#>uG qmf8;7 H>#9qjueY*p,@!K)GU rN.F6nhIק+ǷtP~.}wxozvk_\e߷"!d71Q_~D@/+b, E÷V~<˭ d~ boR#%+Nslo¥xJ|{?$L -ɛttg_{lߣ1s^GJ jctOAf>Uő}kTt::Xᯒ+lG - -{8)5Ol]i1. ~) I%XS||/&!h,ϟavg dg}*IE0(`+}:JWryF\qR9kI4 %XKv~-۠Q}aiL2 }GӇ4WQ -Bxa"uOS,_|pcKT4dWP #= 5Ǜ.w8{e3vQMT7TR"2L>Ϊ4Dd[HBjKVbթ(a0ItL =ԝj2\o<7ℂ(iޒ 7u}CV}Mňn]$ձlCrVr SJFswqMN,fO) -G(ZxƁW2׭HTz6K1\c^Q]tm^d9SKg&]űk<$fCY)1f 'EQ!/NΖmM_U? =z9х\>,}4qx|~De2#6wj߿J~oSǶ0y~6:ʊ|ꩥ-NE7d`dh}v/@GJ,,P>:*)IsYl^JWIPvd{-i[x'-_ r~ @YJg쥟1lmDʨ<N(S4/J7Fr3lT9`"xKwvy'׉/̸ڐz 6@R;߅Of92DS5ٜZvJ\43TeZ5^f9In9OwnH>2C)z{{$Upe5 ۜ7lo0-fyX|*ăK(p΢7+ehb -\}Dɭ g?:>~^%Or|25ݿJ%Z~joPz^(\~TcݜO*p 6u$/c}@ԏ=q>Ft7J;^` CD^(L(*#cIulSYM(dd!8$~d^aRH_'w\yw+l*ܡ /'jkQ!Ic -'Y ~֩@d{%u%B苂\unA[mhDpoM:X(7n+s7$/<TMwԃP񔝰zbTP ;N8[N!ZzO?Iʐ`̿aU+ m~*c 8be)g٧& z;㔖NgҶ in #fW -v_6p09pd$c5i$-?&2uX8E-,9JJVNs-@o3c)vLuRc`z7죊e`D\tr y Q駯;/Ցʭ;m ǷL6yrhDc2mT`H3QS w/zlN&&G| hshtc -K 0/*Z -QnjT #5Y1۰dq6+GSDh^弍C]fM)G3sukt}“ _d*K*GsN*xZ*-wn>=o}78"EBtAJ`25F,-dj|>ZܢQAŀ@0uH:y!p=wC9R6k dѫq-Q3y˭BַGO^$gԥ]l=Yg}~1h.|,ylD"FlaPy&Xiw*<aɫ~4-QZ tԮ-|Ql}U*--r<մ-ё}^W(3YI{EGc,$?tSeӚ0Sm/Y8;5^ Vzz#}:9?)y8t60| & =v c7(+uw$Egy@5 J^ꘇ_-{CxOOC(8('Ο(zm&%"9Vd*Qd`۸p,荅a{~ԆlBhv8ݶw2Z88PbfrfԳ_ۋ:‡cw4Pwн%:^FնǽٹhÝd#0- 0 8&nN zq[-2&^=AAh}L2fr!,ݝHωנ"ܻn.B.Maw L-SłwXQ<fyE_%^vaz64:rG^JNUtC]HaQ'0wX;=|=?#-s%7=LUEMQptYݸ B[ݑOlfS\<'+gC@zB u(] , ZA$NW'1mvѕ&t+!vTk3!}?=pF|֡=ͻ*b7ՠ}JU4n2QEd0᭱a!>֒yX4EH{Tjn[LA$cĤBF-]*&чrF:>X: ު+qNx>4HVR!QV~hZl bz>8*)[sYeOd4ٶrAqpꏊ9ʚ~r^/4 ͖p{,Iofg/;YOqkc>"||Zvp"!U96^\r@>>SJ ZSjQ*`S /bWv٥\v9+u[3EЏEBrN_=!C{q=&fFwuaK%p1%Rp O" -;6eY2 :2(Μ7f]::ۭ%Iҷٌ[z C9p1g -=V1M짇6pb{g+I+.>_iܳ1XqXO[9\d<~mCɉ -snr~#;UoɌ:f}P=6dcŵ)=7=9э grH0S -ސS-ŧB/lLwRx:ɴaLA^ݪD3GNe`gyڒ(vVF c<ұc1}?뿇V&k Me{$0F;+(#a}u^?IjRyeÿ[`M`] n3Y3}\G(>˅|"^u4w>C㿹Y$]K9= f!5lB"fYF%Ж+AkN`jy#6{B`Qx㹥I3Nkܱ]7&.QG0іeF,y&Ʊ.c?a1@-STVkcO9]KEO 9iUްhh&)jv'Q;j'ڋ6F'.d33"" & -],FVOgc拉n؋ cv$}Jq*Qԧ%Pn#Pw>&} oX~qsyH*n>펰noN|;HɈ>st: l]l~7j(ڶ%u4tS?JϤ;=fZb;M/*{A'UFG1q;^M -z G1ĘɲҖ|$cq(VEYzzh^/59G,Ͽ :g03ԅU>;8#юI1V&e!,xXڬhcC.Ӟ4' -%?z`@>v$@GI_j]geH|`:$iMVzƷKvͣqjK*BrQ܌{-͠w91.Ǻ_関کYB9"BĬ?b/L4t%JM i1C9#?}#EPRA$"IYR4dm% {Vf,iejR6R֤t- ;g -)Kiäv;l.}sB 3lqʴxGO5ՑOYBPz5ؾ3s$?Ms>n{IJ>e49=vDTrT> -stream -xڴsx\_>ilLضm 'v&F4ضmۤ7>y^sY^Zr%UzaS{c 'bokdE/boc -`a`bb''u,Č@@'hs01qÓ$v@) y8TF AFn#E+=JE2F&n֖#;S <@h -lf5@]U\E JQX鿰KĄ@ :O5~s:ڇojjJ̌ p:9[m(>G? T #3ɜ|j7{'k h;:A;#9K3oT~$}A5mp?rJJr[#K;#drqcxM)uqrC]NC83=/#1#;gͿ3_3K_܇?رc[OXL`f0}TT3_,?x;y0hfigjsSFu;KG~f@GĂotAci8{9 'v`ji2=_$ɧgROS{;) Q!VpQ0Rddkioa ȵtt*YL,  ۙ?5d!֏ ~g>thbmtv~0p?h (*+NZ'F0rr2g ;;CŦ@`d}\@>3{'`k׈߁(]}ȋ׶Xdo Դ4ؒW]C#-"bE ge0s2Y9|-_;?'t7 Ji -)ϟ)"f8В\IK -,oˠ(M+"´ylM1U3G`PȐ_+">.fhk#rwt?FL^%ꕵB.07c8٠/u/t1b"Wh BezQ XÎ-E{=\BlYѧer+tC*x$8KUڑtx؏^'cTCX%M@4Wɮ%P8Ul c{^ʒEmlKu3ONJOrwDo=嫔} ~d}[a0=)ae  JS =frfov -P{TB~/$DF!llTxVcxXD٧'U`@{€>:6= -\t QFL|ƿ|s͆.Բ - tAr5wr.`MVj B$;vqbkrȴRoX́9"1 -~t,ק\luR0졒ɞGo ĵNb37>X:]hwޭa;g$3rhoNn'Kʵ.,iɞ*6bOg-^z1K[VGٸ͂.!Q.U4@[X9L=˦mh}\VFb~侐UYGs,546"k6IN)Mm6%-_f{|hlQa{sSwI9ή?`~jSNjT"fN$t*vi#x)ZWjF9rV -iFt4~2MhƵU -7d귉۰QiS zsaor#>ʨ\+d8/:ƻ~n~iBy1m/7bk:iTH:>E[D+FqѮ*p⪄)u ٧l*9shrá}ԯxJ# %S3X[^`z8Ifg!䰍6p]̵XoOH>}Q,a-|M2D|r:zLFM0D6آzm8F3%hLΏa;GY` /uPnVxq-,R@$Y 53 8!WDrpw 3zK/DєP),YԪW{`uJ0ӧx/+o݉'/ڼZkHs*{}] /3$ %y*F\Q+C6um|^~i - cDl(XcfsV(Okz\9J{F(uRk2e@_aNFv i -F$)^<߭#6g],ưHa_sO{ⳇǷ*70Y NnwF$FaMB ::LaKGH1)n7TP*òQog@bP5AEd1tMCz65ȍؒk['_9v4 ^luJ(PϥRPwjŰ lۛ' A*V| <l XqWwO 4 -)C̹>FDp~dkT!ҍrCqS5>!(u7<;{1 ai9SHb\-k ,ng\cfBy'}- Y{e}$o<Ōw:I\ QRbr]cqY2zjY(YB[Udga$EemlH="a'݆j]LSs -jyV}0ƌcjTc-FX$ϷCd(P)`Ġ]@cZ^dyoP5F#IgEꎙg>${YOvd  r4\"QP*ĉn[cQ7 ȍwe)HCSF&\*dj\ Nk|:~Y2Q%޿245rR"D-/G -0eD_{CW)seodόU7`] ?'4PaO uL29Axi e =^+\Lޜճ B.l]nS=kR=m L iV0̸e 'sx`.ATD顚w"'&/v 4(ѬYqҸ94NX%JcwRF" -I픝7D9<{vb`{$3= ]T'o,N&.TU6<=ʓBs%äaUѐ;mHP,ri%QsJ묄uصP Cn~=]dE1hg:lBr켙8{B)1lГRۖ~8˗xpV~:+*?)dqB8>:-`3%+ RBܾXeQ[앝6?覙 1 6[6}7lG k)L?&$?V/ 8(|)kR6M:ÞGhO"ޘ/ug)7;~3UP}.c(L]840|tcj?N"82NA29&D[?)cvH:ᷣ*S=Okڌx7HCXu ^q -<.rxIxVpwKia;o5İZAV=0X@9"8\#h-y9ec뷥qsFthm>߿VxG0ʹͽ-jx{s~>ZiH]Ԧ`bZ^i^x5\gHxRȓi3zk|4Ҹ7GffV J/Q)3{Q f g;/lcd)V^%{>y(0Ux"H7 0.ZX19Ϛ^o`0= -M6|r$\45ND$X2f2 Coyt!(ѐmy b25ӏrP"Z<05 79ٲG<2ا4(?)J蹾0Ucghߪ{{Yl(W7D⤑<"{d]E/.yYj)Wx`tKJQ𪷵Q5:ʽ<٩gF{MXw *i){ |dW.i9~BO13죓qs# ,x;Uħ_&) ~3/QϨO2 9&{<  QMȣ $VՇs& h_*0`O 5q hA!Yeqs NᾓFi\X5i=SXԫm-z3"ņNX&b'/%.uBd5.` {s1[*}؅yFW%0e.2T)6<-Ĕ߃siYb30Z$*Пݓ Rd`5Sm^;l#JLc-77>2qSf -~$Nl#_6\< bo3|:ڐ݅qctuoSΤ|3Zi'_7OeO): ieN[ҏ&.|7џ7zS6)J/49)ͬg(Eu]쯠uX]~C5VG^3O;DOhKRς53YWš7铠_#e:ŲRl2F/AQC(v@[i #dTa0|Mn{H8$j弤;I̓WrrO'eEA -ѺSw4g I_h|9Ih|DJL?MjbϢuFSM?YơXDLVe,!."?:il-7X!7ANf ܃Чh7;ki=!mJ}E01cjq#?y48ϗYs T%>[oaҨ(uri'Za-/J> #46 n"g--!'Rkn8,? F$׊34> -F{VŪ8+KCFcPUZ: Y;mz;6 Ww櫲 j]3tk\䀬 8;֠_e-ҷÿqo]mV;v!XWc{2ܙٜu]^˹60OL"+c&xGe#|nO%_qj)3(KM)ƵT&kGz^@~$56Jgƣ#19(D}2PW"0g"sD,h9'D5MtZӦ#hΜwFoR8XO`'^knʏy'zF5lr#Y㢍$\pR(7{ -g4?Bx2=w $L.Bt>.Ւ/gKj-`C/X"R5 93EYUM}@o;^E)wYKm(mad?qg\x:TBYM}UtIzi(![ tVrH\# 6ͻ$/!5(9} ]Uʈ%sA_IQ -|3ٻL1 - +<*0֧k9iQ))mʊ]ψegW8_д<Voed^UWM~P$zLLw,ۘmVM jI1 rZe͘#AkKl:jtHׂee>`I_:5;YkD잯 Y |wIDAyk#)ـd &#>W lNCfg#[_<-u'aAN1% 5ⅱV'C؜hxj鳇L2/M$,U,! !TEO KƆ9Ҧ3zIo|6g(XvF -'a2nQSVցolL)5'?S2}c>7O0 -ᤧo7f4Hպf{4"]3g>O2G#rNi/ġվoY0neC'ca<͗O O#q'lwr/V%yI 뽧8vd|LDH$!n -ysLٟ* ϳFcҗ}{~'W:L ~6d&3L>rs93"0=Hs:af_}* >vW_AmvE=MnBOϭC0I3I\QMZJ Sďȉ&@3(c:+S'5pIӦpnu$7?M%5IԢ?lkJfzIXBnxr)ta[%{;ct]W^b|]Y"iqIv v+`=һLyE$ݎ -#k;oe2vyKs [|7Upbs'?ղ!f_'orgDz>_ݲL1M+Z|B,4z 8e)fG"#A @DAΫ!#7l"C],=i*$ӧ>2ka0Ń9VQoeC-ckFD(6qSAlV. ,_~&%uOA8&<:L!⹈X`@E-r An%Ѽéa^u 5DH&N} Y$-|V&:v@OOݮ񳿵̻Ro[JG? [;W|ڝBFlmB~Z 16"ڧ2OvTC;& q h{v\ oAٮn~!@GBeقjYiAۭ<&1uy,e7GK[M?yKPw̳O-N qe +1C]i>Dh\s߮wa+wNy6x/gF%oT|b5t%A{T47 cSk=.oVa耓~^=>!7q]wh%!.Ԇqt s9$F-$,w3z&|Kߞsp'RF̫#&`:hu*4 Ug\3ʂ8\ʊ砷UQ`.F_}n50T,82SgDӘ[_JvU͟OMqP>4&+-: -.{Trx廲_?+x\R+֣/4jAg}~hs546)>yg@(fF9xH¶'Aܙ#gb<4)䃎^_|Ю|9B[c 9v a:3G*ܠL_d}Ⱦ]vZe Z#ǰݵj,/_iJX@l-!oxpBf: me; : Mca, >]3Up)^o2T!DH!/w*NQ n:fέ|؎µ#ȘµYJ2wA]ZuvR~CC;dq+Z?@vR^.ِh j9&yON)HwkȏY 3/3뙃!*K=캏J# |4㤡*;ZCpD%W|( h1,N{i'r;V_a0ӦAyi=׽ubMM" CY{ B0̈́ &HԌf=>TB[ީeZ:]\63^nk3K;D^7Lm[@_^K<7Q$tO{5-]Aq4j8h~%k-5قRsADC:|vO.,JmQ` -c 2GY%r E5%!3?,p MXbƒIvm }bZ{exc\ˉnshQ2L]/JN+DkaXʆ80`,cW7Ϊ6&>*{i҈4ܤɁlө?9UtQ|̐᧝ЯmG5zӺ+ԋϜ ?@zp˜Y*^JjYvN:95$bV:)<2=kNZڴ,~ -}/_/p ggaj*d6SDj⃃t059-se=AUϗܹVl:1& ۳o`>=ȟaFj+sb )зCjHy1;FJk\Y8Gߩ_au5#t"uE:$sa럝̧fB K=҈ Q> 3%uķ[D*^EB ^u)DaӼս[.m!'C̽Q5R 8EBdZ#S͈;I_͔LD-Y̿0K;:h׶aŤTPs&iPkB3"QPCľHflHZ1[ ot{b]8.©rܿbft4rڏqiݑ:.fAy - :fr_IAϞVE K46|S7:FQ~oAv=M3io-ߐFat0b(hݘW(;4PKyfX[#g׭D4m5m-W<~J.{W4ELh$,V#C_|ILGh?;Px*=]f́$)|QRח,7y~qɓf9vBL1к^- ,̑T?@`eZh;ꈦʖϺRlnZ͍ٖXx oɯUT>rl] X8ٌ9*;hWiɎ<.f(\)g^o/.Q҆1Q)Uh~|u$EOg jdNx]SzwK/b~1% ®mP*׺K9\O^g{e-g ~gC~$ wcЁ 悀 ~Frq?#Ų Ta(߃ ~eOk"F ] -^<]3=X<U  ح)+%-"u*G(|-3RfߌDx tb֥1 ~Y# Cu$q0K’׺H^Cc'$yaLeֿx&clꏕy{i{^QK\\vy@j( dkWl#=( -ĵ͚)luvkFe_vjU(K}poVMBlYVnן+p#_̽zfǔ0 -,{]>K~os$q8gP+I#oc֪֗B:4yd5=Y. 3{'o)7h`9Jt` -󄩠1Q? 3Y8S Aµn >>=WAx v`(& s9_b@e_Ȃ˱j4?MY_…!G'SzTCz 1QJcf$s`9|𵽔aA7N^ \ {l ‰AZ>tF:ʆ5yi*Je 3kbū#'\yX-f=YYζ"JS'SB3u$؋$^cAIaTdgIɉo?`꬝`z`1}gc&p \'V3TpOn"] %)q̎ -JŶķ5xcLtpp{PCGuת|*axaRJ+d.\r i$*CG{No[=.Pl:{eTX"oD-yu09J ՕKFMo6}J ^|&l =nZOsZo@>-HB2T25R֘.D8Pd${=Di4YٜBr=&043$ԏ~=+"u_kE{CNif[=ӟoQDJ#J f5WK@7+UKk}/l8O|JGk |\5V 7abn?'4 I|e*ߤn1ԖNi.;|V=OaZrE_}pr~hǎiӞ1>Sbse2H"8#`קPyJE ɻ$UɢLBCAċG2**%E".eN#rY۩#d;"TP /G:c'-ɀpo^.L -ǁثYsV=}8#ș#;Wtvْ.}G8?y=y $)+Y~>/M%1)I`Fǹג*ΉL? .y_Dq -)/)aa|㣙w@h-ϋMB:鿼o?׻=N(h_, -Wup־s/.+O6zQ:[XlH"g͖~CO)"F徑 kFm `1@D.rj+ [e74.Eě4 jY ,LLE7f!pc %7E,uD1@:)aJ̮kH6 (2Rɕ >rL,iE,2ׄjlJ%޼aF}jh'h*pac[JwM eߜ=l7TyMۡ.tcArB鉄;@ 1/r+-Ql[iG>mO䭆™Py_+ iǷ%n{OVf|fq-6vExvr=ǣzJS|dxF'|CZ^##T6Rv]W0?i'FaAk'*[>Fe 0^u% hZX|eo4>=]Y|eC0"Ҋ;VTMR^=x/|xRήy#z[lS;- }P!^_08tyywEoA9M57R>`[Uϼq~IOW iu$ ԓZ14" LYᅷiU]·@)l_h@вsQS]^?Xctŗ"*lSV@_~JGf٦uF|ԫ+l?—1t;8RbIRL o FLYc`uQ+71P>=8=t}6~%m=F7Vh@^/Z"`4@P[ бădz=+emC&&єEfy1yٶUPY:Բ4TBO[ǚ;I+)M$Fi6qt%:XX,i%Xi1|1G#_NCLZRJZuUkYzvku}\3Q hUՋ -Ր}ҕpG<<4N{+V -#=!=;9:ayD=\cё߁2Tn%_GOnS#ox3lnQOSӟjԐf͂2qSv;>OY:{m -PQ5]]$'澚=ζtHNЇ'O*/a CCHPE?٧zB@,`Ow΅qS([@rm4SJ7؀Ț]6Ɛb&LWKgf_St., ]t} rf>yRHἌM ۊ662X*C* GA -1gv -śPV 'gKd@ &Yp\ 7svY*lfl{|sH\;=&֥/ELJzCeΡr;Y"y೿H'L+x#9^ɐV08z9t(`\ŮtyAgu^+>C03 ?9WNnm UY$[6|dJ8 Pw' '> جbUm0[qD4zz1瑴P޽^ƌ3v=bGQ6+z)3u}R ?d% -endstream -endobj -343 0 obj -<< -/Length 2561 -/Length1 1689 -/Length2 1522 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڵT{J t>jS()u -?*(4>~~>@ 1P$B* BQ.% w&2層RRC0"|m )PA*4{}f(_/wQӇTw0Dj|7`.:uǤ\NV0THPYqR1 -ŤQH@ MPB/!xECpL0Iæ ktC\ǃ(Dh^I(ajʇW jkRTBRƆE~39EM5RF1ؾ8II3qcjy(e2_Dc9*S`H6 9"cCh$fţh&-ɵaqx@zdk /6\d$ -jLjY[j? IIp)E@Q38:x KI^ -4lTW -'s9Ҙfil QhY =ͷf-" <RNIb.Sw_a0{/]\`lmloI(N2zhTKݚfa0:onj7Ks?R6̞5Pn,;K3h[}\^~gCXtpY=ׇ i4#=wASa+p~a3&ޅšGl~_U76=rohf5euNPqO&d3:vڨfxyAś u5+_3V ѓW.|յ5^Yhjd[xWZڋ69^_dq#bU[nvl0LGkܟ~1-r'dVkp 4pʴLgt>X랅VvkV!FuƆپV~*;m`ְ# #Ysgھw|3'OQL+_zr^bbKhu/1Vty؎t~;WF*ó2qFK?Qygm_9- -qjkRoy&s_jSO[,NX~{/D{'nó4ʰܜk ;?xG{E^\W,ߤ?UCIo(}Aƪw8Xbe,txăWz+zy2ME֟-6g|t8#;nrfeVHG̬2L{3Bmf]!҈p}^[0$ў] }GqA5-WIU6պLGƥ5@ӎ9揢J_,}cl6ݒ;3 䭃"o vk_и'ìo;u0|> -stream -xڵT{\LvI1QYR6SS*SHjJhN3gf9g9S. B7eV&hlEmZTr۵L,9|_l՝^S4;bAcBj ,TJ 9(ffr pНp2r@0(fFa9p`8U`:4 6ᐂpèAaK"P5T|!R|Kc@E` A 3W940G)aX<8 oe8xq?F B%d"Lg1l&ݖHX@ȶ_`3'OЈTj \lmhBar!M&b@D.@Gx0$/l)%$;0) -F)4l?B(@• -̟? J#ePILAR3nFn -D++@H`<3X>^LOh0Gh pttt!uDTJVPH<'lCbBc+@P<Rf"JCa| a8<-ZL'%q12LD!xQbP$ p1rE;> CTA` $]`aO#ɒZ>JT (N3s_RJ$wbD/_ĭI\ -I! -/$'&yCLB 4;'aO9mBՄ'#B4.` q؁-qsCL`&*b!RQ;::!|>NLN!l1Z2S>!pi\U? !ʑvf脝}Z7'lCuT :}#pvfč $~\c `8Qn\6:Rj޹gP?/_캐qPf§)~UG5<<ҧ> -stream -xڵTiXWVEQD -VT( dKK1@$$, -"C2 C43P -".T -" -TVQ* -ڪH+3A?'dB, -Pdذ Ѽ8Y&聰8V -)akcC33sW`D,t , -V2ęf`V~!\qr , " zn#(lIc8"&vt:Uvc_H * .cJ҈ Qp4$Lp(qy+pYPϒA w?k`9^x|$~"'T2JrlӚ`5G㰙;hdH4 E4AYJ! 3R >~4%򭀥XTHIDc~FqJƜ2J2# ұpm!\d0 -2XDjl X}R-t7Y4>R?1ſ}-P |" Dԙ!exrx|=HvP"4T= ,f:&P:BwL&#Q4>q!p )T(NA+Boba79& `oD[S5ʡLLR/@Iq8Ck`@(bh愈 EOMSayL"yz# [9O -1Tf8,?37gT`'v|4$Cq?..p[c -$CpOD B=F݇ș`b) ;1O5mRRՄP;9sHPǁSIbƁ'OW-i9";Hh6@lA<ԽVi(F)@K$Qt2,NH5ټCҬy! !*U )&i'oV}vsTŀ 0lC{DUkj `:&29fOUʑDN~{3㏣_Ol317] $e`~ެU֡fɳ['wk{"\dJ&q-9Y3(Z|56v.j\w6Qݦŵ7j)u1 ƧMOD4=Yb;Y~Ws ̨!վHsS]/v&;e= r7r}3>eYVd'kM@ jv_=s~03*3'k% i)< q75^s_ ?;H+6^x<<% ѧ¶sgŰ硁4A#EJ~봰9AO+ DiC3k-\NP/Yґm -TБW{Zwo<:s[2Էoߠ~Y5D&Q"_ +zʎZpUtzus=XĀn}O\ӳYR`1$G-%V.~=.[=F/>,T8O4Uҝ )VZ!aWeۇbU&inam̞J[sexBqR.&.s!nOL 77P[x0zp8c-B#KNe^OVg$ELF! -isf^1/=ӝtq-R%Sջo [L{|B޳ ܸ}A埖$fȦ%hPToh,L*I?N[Vm4EvC_u5=4#Oɉ9-~PffV̞V+[_; -~־6]:0f#grуK^7^,17~.Z #Yy:\,\2Kκ\QN7wN"wl+ElFWo*l,œ>şl:WIK|=ny[cgSԹv=H17S=,xiRy{[rU7mVSkmMwm&%u{k)q\m_?/|`8J}Bc广}{~G:M)_.]a?Ѽ/jhGY'OuP{yIc1k7m kP0waIQќۂ2&%u@o×L.z1c+oLbËtK9Ud^iJQU.ORf0~-@'UEv2EbD=8!fv,lm|`Pt55m -s\F|=Xkt~oMXhК$7$ki *%yi~N%\?*O -endstream -endobj -346 0 obj -<< -/Length 20524 -/Length1 1755 -/Length2 19389 -/Length3 0 -/Filter /FlateDecode ->> -stream -xڴuP6bšhpwZ݃CNq(w(Zw?d"˟wḡDQAd فXyr [#;Q#{'fe#Bhى.@̃@ߍcwlndP#( FNf=Ddhino"#k% (+- ;1 25j**Ie5E* ETEUM &,*$TTU; WVRga{ d`|Gohf  --`{^&&WWWFsg'0#ќ|NW5h1v[d&@;' п|z׃/F; e,UTYځvFv&`#hJ/@rere d6F91#;g'< @ /z3G'',/-! N<;9{wnp3sXx$3ھvB>1>AL;@vF3K;S30ugRtpJẃ`3X0-oYi8-̀oNF.@)!pL-M_Kۙ h2 Jn -.ϟ*a<) 6Ձ+(XNYI+ҤIJyhM1U1GVgT H[+"9*fIom#tt wnYZ.kK3 *^'T'$ftQ2a^0 }o7.dd KƾxyqYUXUOBepNS4|%%Q9 qXz"o6bid? ~ԫ9jc"9IS U - -%'Gv=*t:n*=r?M#>)с#>4NW>\8E -)nA*sO0 |S$<5/E81f -q:Dq |2'>QN#X&4M{YGf rR~j{WMe@.Nϝ.@a70IeU3:m ҏKnu!:&ӂ{/ ^#{ї&I)ȝ3SgVH&KAؚ cCRb{SNHwDӢCo72W~Оp2WB I9Ό x^{^I,!+bXH _rYD8j=/kjR`;$C~L8vEYRaYvrҠP (*ː/g6р04 hĶO+&TMak*9x }j`$"|R97EM ?قK~T"l>i.S[ ǐh+ែnDqotڧpi*0efr?gDue2,~ ,n/#VY$l_[y|gf'x^48}QV_PrpY,aD ]^B Sʌ[oueDH>]!mq]vkoUPlAшL;X׽:w"%6!=b *i{M_Ce:`W'/h$7SPr=-/)o3KZ*:̨HE;o[ S ZuxΈS['+0uQ>g*BC>U:[&x_z>zP^k( v\Ԥ[#7u0[WR4-q '"L̿Sp_VUwQ.i8Ƅ0?4suMJP%PcQq-K逡 K %-;fU>~{pء$mʙ. IҧZuYӨD(Tln=SՉKYQ=Kџ%HL{gڱ+z,Ȇ`q#+^ʒ{za?rq3o33q5tjbbO N 'M4.dCXcHbCRJ17P!$G&υd* 5*lLȸ8n9a찰,fN7Eэ#dę36!~56,~ܶ7&l [+&or:Bʤzdw͢:3GN^! K+&`tzX3vVH:g:uX(4-"P1(M| Ԓ/;jTqtۃ+U`Wʚ8J :l]gfeSǍDe`s&?;^p8KoNdg~r`nIY9WھABp:tR'X_\`%H?'2r[O> ՗;ϏwUөvq{i{og4#:2V$rˏ: sV&m@Np.%̘ulvPzbF -ܰFl.O>g,Ԑ(#r]^cURFڟThERׁE m-g ƒ)r -qeC\(-nރN>ʮك=k\l\3R[}U5[T]Kα24-Wߖt. R-( D W59_y(VHC(QڑqGDmBKj/"*cQOarGmC$E-F>63i+GJqҳ,b35!g>`(nϢczl%L{B[[?R򄒋?БxxVK h]'YVgJ7kOV!za -SpS 70هpȞg -۪9PQW3n=֤ۃ<•ܵ+k=~<2vv\yjc9tpf}>JWDt6"~m"܁:/{A3vō[rYmpaϒ#hXg%vDKu!}aǬ;@RzNy jI -0ΎV :4-}nRĒiγ5y.@Xk%uR 씗F}|ŧUϢ#WgX2 +jCu.QSс3D9uPڜzâj7iJi|(+QvaOJ3O|xx4k$Cx&&1iq e&G%)_QW;3 N*/qw{{yEzgee@]~Ѭ; ~MxE`I4CTs -ezg# :IRN+kϒ2MIx2t? 'ɣk78>Xcݸ<3cR3 -PRB =:!Of/ cll)0)^aܭ.n^?l`z qDP#\玄^6{I3s;V~ ,Y(oX"&@ 彩s]̩pgɟ6u\l)td2|yL!v',E ,e onm_Rf? -|?>C!w:\<,6esYiTfѩ2"*遡ҝh|BHͽRSsAI9 /E[QI9仦>CU[3^VEҫPBI~Q/߄t)f88ONJ%HG3: *KBs\0DTXQ78[-F}ŀo_i"@YUFvO@44dqI& -$y\DJ,iqʉ3{j%ںp8C΄uĎ'@lT_+w8ZúYJU6C̜L8,xQ[g8i2$pn(&7c?vٖmN ʮm1߰_,S~JCy¡Aۆ/CrXs[tjК\ = 1ϪݪԷ--8<^G{mfYwZở\ikS뛕 @lɅ8CjUptyPrvz)}!ӫ_uU* }ŗF"qF[7!.u/U÷4 />0QM?VpdHS/7rWBԦ$۳M.쯰gA<@; -/jwY̪ȱY̫MG=bL@J:PK#qU*Z>,_U=[(%n->>?NE,X< W°剭TwNhT;=|^X;3ڶ}UƎ.jWEݓ[6cRk8>5z|P¡PO5硻B>њM6LI#JfZ*.&U+:H ,7pPJL/̤\czAï76| -Yeiz-uA"d:]_zFr)EALB_hB䗭p CeA6-0}Wau;З# ~}z!ȐF$Ő( n#u3ܵm"6<vr\.>Fm|Ki7Q/`=.ubF&w~ңPYw/&  '֜S׵DS.'=.9ɬ ,(KoG]S/_֢ |J^} _Wbn"u -Z92oϴ  -5wZ4Tfܩ )b%nsLGoJOV -vw'598}迅 Z>5`@|Z /oo=oAKXOTv px&g¹ PlY0g|K#lɧ8k?lՖ rxy֠ -V$ZTQC~D&rmU`A6Į}bϸ|ׂcGaUĚH4 -"Ge\jܶ1İ*g=}fEY{W!1hE0BR)UE"Ή%'}j$yg祬1P} (lB)\猤P!X2sCa yvGa%6mUďr☾@Z \ur?>Lܑen sݰݗ*j?7Ky捹BЇV+cTD|3[պ -X-`Php|"3U@Wo`H;u4ूKk%Cp.Ro[zJ~NQ 0s]vFljuv$MJwGN1hw޶y;Gj}6otP# Fdrnَƒn3`t?:YVy\I ȝtT3cBA\Tifʚ>11izΦ﫣4]ERTKB#M! (iJ iY62?1UuA 2"9c*< -qf}B - z#_--rGE':VؾuU W}Q Q.Is>jIfLC;a2`U;i2ħ$wڒHi!z;kV| -/5ɩ:s(n2We2yc^Uti 6_,"tQs*AвPI 3 -iʠ$ -5Q;V4.Ր.KɧCK:ף% r=uo/cnyw1I`:َ[bY"%XC0+j<%,atMɻd/ƚ+au6xw#bWn.tgWIvViwj6Ђ Q&]/&{]/hp[=pԎ:ܒ։'Z`Hc=A@uN{s]ﻯLT>4+DnƉbg=V2Wv%Kk닄q?t(fw[4CMrDo<%Ƌd,sUC -bnieFCjaY>ƻ*?]9Q -_y]ВEH8wRDh׊686>~"2^GfUXFD$FJm^}\!l>.>*րf2oh_c Q@xecR|E57̺W6sf9%)3K ֱ=ptӽ;y[mqgl㛄tTa9V+Z N _yי-u}$ -yr˯~G -;ҫs}BaIWn|yE:>`M^ϵVaFF8D 'FxߒTS{K>/#{=r&[-:PBLiwB%#|P{Ys[VEh*7&]71ei8pv5y3~|#QE[E~\tYs1`00p -SAD$$`ٖE 7cvK/ -HvtϩY3Fض ym +n!r+ܒe -ϣ -'dD+u?NUAlm٭ߨlZDهUn?fQ,ϙ,Ǜ(#/%Nd\;n&peVb9H]HfoaQѬ+p-9yvs3" 6րJ+.-,Ǜ#F,8"pq x -D!,BξbEmߕ$GM(r -D!n #өJ˨@==Kb͓ [ge1r427N4huܜ7ohC4_i5+Sgsy;axї_ƇW, 5@rtt!!L1 v*nЛN`Jyt֮O<ͤR[gtJlTtor?#OˌFݿފ|Ww -K;Gvrn-f%jtU`mia0$oʃDQaA3[3>j/]}Ő,R2N5bJ HbO!*a$.<ȳ -{SPPX>y@ -MsqbD|UVQ\|oza/IHc8HۦDŽ+Dv@ަp -Gk0v}a1Yi/&3Z@1 ~خBJ&~JJO+U?/hTp8v#6Y)DM -iMsI@,pDzAG[x<1fq)§ZE)#ck̫: vƍ{6E EWhڕW8:W.!rw]7QLMJwВ= -MWIJ-Yv4.#uj?Ler yOi1+^|TCa6o릀Wzm}Y-+i_ct,o*7& [JXKXabi]{"Tw_U%TS)71P:Fz7jZ qRwJJ(ϓ^]ux+lQ|N:HWOAo%L^]fH։._p#8$*9=q~fX}Ix~CQB%DX&߳}E2TN,.DrC͏UHSl\'F O_m0qwnAbupgNŰDw+-PujV#ȶ##$w۾P&tm(>Edk?\vЦ9"K̹l\~-WdDM<}]JRduezKozѤ_~}h2P)dgaeq\ "КB ǹjs%Fa7d,ڎ 3hTVkyb/ٮ˹T CU#n_putM٭zՒщ6%&VI<:}iLs[SЈ$nx pL;g+1aUaM:Tdfe;lFG/K 9o:P LdbE|QeO=旯AT{K|)(3wW3U qt}0q9Kdwf?C,*vԣG^F,%IaC/:BY|M(&p^5(w`p%{ݠRT7]]{y]>{~LLrfřhiN"PògX3^y늬2'ؿgG=J_*Um;`jE6prbQt2q#}p0 SدǡV^WnRyxKk]ƹ"ig-B+*>a[3Cj"7^1CGг2ǭ$‚v*½!˜/f#`s}LjEwteWc?`,B^؃XRG\0h‡@7X&/" 0N\ݏՁҫ+qGڎ~ r!d/+҂`3NO'"dbɅsRos0+*y}o~(:JpnϺ屮̌g,ffl*ֲa<9_ Y'82xens9tlCJ!QS3N'6Z9F5-aD˟,Q$xN}M!9?eKzjqOx|"sc rd+CP]oH"5Tw÷Z*rOKZgamgZo*%KZ!M/OZ Lr@4ڻS1DX2+xX^xk!QU.M5 prEC}(!1I.Ơrl31^,| -6FΜYj(H(x6IeUDga>.5ϥ|Y?ziKckznU02EFS&~Ul8ke5!8TE.]WcӟT"ʷxCj%uӗ ӰHpn#ze,Z5bl㟗sQ O!ͩ\4+B#9!ҩɖ%m wZobil7^Uʎ &->چPE[+wY:J~l| U;/UM+/`?jQ8ۼ蚿2Lj%gz,fnוu+ids]$=7:]| I-R. &O~7d9@m5 }LÌKR$IoieC3,V$~ww.TIQgÃp,}F:W &q8,!Z!.llO+ThVWf(N6pdhk-V4PJ/ޟ@3$.S=Q"T~|z -sDvzK9 DIzkĭ y-2-Zq -`y}fwAb3lWaL!$8#хGPtP\N.n!taߣo%Cɓ$l6rîy $,>wc+k\"V310‹,W7L!mk[ [u/[9˘ -Dmj3ޢlI\Bil o}::{Vd65=MRks v>dI%\6Zzx 9 f cTwb h 2:i\!jQBJIHtZn uᔘbOUdZJ&Vt͂g=ςFѹ<;W, - 1-6[Q>LbpL״d)10cu1zm -t$)b ru!IG~T+0杻kAk%bh1mkm^;/ZDz -V#tOO HRXRֲ$[!uD_>J-: ޳=/-GL}!kͣILڥz<[W~Tfw/ Pnx48bR܏Mm5Uv4 3!T&/g` *J!e(sSsE3ؗ?|".x$ι[.{s?dmOަkJ*#;c `&ܪM* 9X -!%7F}sQFŜ79#D,_=d]3DujQh>1qnw;/гi9jp=|xpU-)1pLœmronsPRX7F#ҕFN/5]{XWRjK7YLB0g(a@&Ii#̚mqN%+[vy%NJx0 -0rwySʩ+Gom[cf'1^!|ö" J"!@pl~#a8~^Kc"eќ3/o7;r .: -FES>g6)ԡO,d<$>&({gsUZ - Ύ\Dp:AkTivB"Kmb݀GPfI=_TI4N =EͼÕ lD>AFIorV晴 -<3w/~#E7O>?D$=8%D*|ѥ=4o|nu3fptʍqH4%q7{Q #Xh%c_Mѡ'fV[Y 7+n9;Yh - 1L[i8cC諘z(yǓh~KGQ -{*C@窿\1'x+|Da*ďQ]HdyV$`Y_RQT<^BDV)4a -iw+2bgr8!:JV?\ }~\M4ur4(@d.oGX:_=fZFUm#qvOdLnX5!n6F섩yl>TSfg|jq -'hFo %$Y ϴ|F -NC&jg,֗/p zw =ɛۆZ1rk0B^xY^i@|i>0ձ#ÙSɂ'6Sn>a|1pɗ;|{tV;4i6|qrawLd<D`C`8(TK0-(?\;hM6_<#WKMzjŇ }6:/'v-PVuE'ډ/,p.Jn oeh=a`.t<GUyK1y!{EDyx_"B: a~9ZA硁06L%& Y2^4+KƽW /a "`^SLWi) @_eʒ(NP&I=0!8 ܋`al4Co]d~MbEs5YQ6ov5;)`{`?`}A77[B{9 *};oZ'+xSt0bb: aUh ,3.Xr(f~h2џkht{-ͥuf/GYuEynl)iq"k926/9" }j=K?4+6J*D ݝ2m2' ,&2ja`޾ T4̗r@Ks,B-o=LT%DOd\;!V kU18lzaw%5`F5x+0]ؿVh}4#ND Siy)ֻ$?vC#ȊZ`TܐXNl̓`7Uww!dG(iDЍ~aXfST1Dfww9B6$ʗTĶ;ZlMh,5c[SQFoHh2[޶z\pT:UKbE ߐy:SOfm_l&N>V^-6K]q'qt%g[F%տS\4Ֆ>$`)lnnԻ%MlSvF? n :JylKw-;\;Rď#O@g+=[^i454pHΏ",K$FB]+W_OTzF};< kmol1=:Ky]PJ&N;YNE}׭)NӸY"~Oj5*gV_ZδIb 2>0kd5bRmc- !&9?1{3OTqHe4v;*Y ֘F֑hFR3Ss]A"EX=Z#d9WcuBDJάN]̨% %2Z-i\ޗ7\JBYgb *3C^&t) 8H;bX១[ -)}q mYZU$|+U@3N|ebK v!WX?d}`b6{* ǧ,>xc@(q-38A!(<);BO̘V7'C,J*e/u$1=kWg;6ņ_vCr1ޫCץFp_O4\̪|4z4n0ZrUUcu58DcM7JJqoٹBX Lc&0& 9k5 -0@z ӧ' |O'v`+ҟ >4WKQ5)z+>| K05Qh&讻t}q.-?. -endstream -endobj -347 0 obj -<< -/Panose () ->> -endobj -348 0 obj -<< -/Length 3788 -/Subtype /CIDFontType0C -/Filter /FlateDecode ->> -stream -xڅX Xڞd&j13:*.ZmEqE+-. v% "l]@MEK/RmZV֥ҧv7z}m?O2̜;#d2J"ٸf} <CGĉHɋ<% -sRq /VJ/)eqkDČql?*sdJwdHƌӣ?c/2kƸSDXxv<QFs|TV]1SjvLF#ӌ`C>:Tk n0j xmɘLxhҒ!ڠ7hcC &mBAlc16Aozzj6,HC:ojϹ)QoN*<+&*z"my`@j/ ^ ='8uE(D9Ts'9| ,PoEC͙Qܲгui1窻80'p[vl FJp\yLOᓳ{q Sl{o yyw4c;kw)V`O -(U%_E3%$$2:8akqOcCőb! -+;* uȰ25ۚ}9ܔX'Ɵ?Sc61C۴Z;cΠֵ Z+:Sm l-~SPH@܅Svg+v%qxr>pxfY_frF\a'cg<ԃCW=MZs=,7 -UI^-xT,5Hr&-G.\W7KNشzo$n`pi_?>WGX{Sn.34Z */lZ<-槧NmEnKjT I5;#WڮqwMתc!S>xO`ύN3 ]pTO/ڻ̓0ٍ -A5\mT/ Z⍥Lb~Mw?1-649h; 3pVfͷ2LloM9RV>gO«'#m?9ۀ`W9C5^O7J +kB+E -5Nktԛ͌n}P١͂5+Ϛǥ̝fQRkAi1_Q}C,FPw#VK~b5¶;/D0@DmB7=u\58IʡW -ubTdBg]X&)l*s&jXF#~D6!vI_3i`žJ`wG )աX&!Sb >j "ك{`e`-L5 !m_ -2D+ vr;5A1 y{ss-S䢎aFm= SU^yFcoW֧h/% i&MshB%DŽ -dT';zJ%r9%ӘK(c7f52ϑo{:H {Ϲܮ铡܁iﰛlL6ǰOK_9,yYT*7uYJcӪ$!h^sO%g ?>L`rxUO?ͷ4_\&}^c8Z9(by/s&al'sj찏g@sv<smz)PɋWtugywDoU{k0lrke{I~v EFݳ魠W"|҄/=? ;Va3Elh;~_ 4GaяJ/Ce<;lj>XDRʲ5eg5C -\j Q9W _15hWed)a\ s06%GieN9ikx@~#8;`'̐fUYdEU#5Sj<"loti>^s>"2*!ߘ-bGoϵ(PVfg!:̟A&mEnJw,HTt6l>6VFnGgqi=q2ah,ܺU< -3lf2S?}pݶiև$q]"^'=ׂXɌ,,9KiKRŮC"K%֜zzDy𫼲hE< 6qIqT 7Irۛ9o Y 55(3wu NAF'Y48V6I74V5qD8k>r5ؕÛq\TZ`jLtUa9~ŵ:]s+@4FxS2VS&gW>6;kK`r)`T@Q ռ`߾kAqQ k֒}֒5dE4 -endstream -endobj -349 0 obj -<< -/Length 24 -/Filter /FlateDecode ->> -stream -xk``4xJEê' ) -endstream -endobj -350 0 obj -<< -/Panose () ->> -endobj -351 0 obj -<< -/Length 5797 -/Subtype /CIDFontType0C -/Filter /FlateDecode ->> -stream -xڵYyXޞA :3}{ 7ְ-! *,M[HzmRiV3ĥ޶wgfy_=&,Xdު%c-_5}YJ>\ G1p'r{VG^/thH  0Ajw 8z'Ǒn 0gmF`?b߭ [$ K>i^tL",$4N:z.r~GI?t.wNIGEGId`ittlT)E*F9M64L)MVDH -\比IUQA24.T&]{tatTtYX,J)'*e2ih\\̌T!nъ;/)[o•+֎[dނ I Y\ъH96{{`cqxl"MbӰLl6ļyBl[-Vck0ol-یb~?aXE`X X"K?aH_ Yȧ?f`3  nY0F 쯊J\?u} L_s1[2;t?}'.\J%M3rLV4Bۚ &+ DYNmfR{Kk~jV> -K10o ADC176])wKJ mUw`൛"K薜Wxp͑\nAKH3Sp˒Ȋ5e5EY [}'Lԉ A`|vvY0_PxyX9Mx?FU^fٮ¶m -hfZ$X2j-m!J&HlS }\A5u)VVNE_\[o u/[Ѱxڏ?ecq`3IBz. -<O .CA;zI2+|FRiOQSƓc)MF)h\> tJm 4[o')L4&SGE}L8'é쾢iddlYBmME?i 04< Y is,ْe[`hbLlliL=y&0+zL^Cfjr܁8mˣ=s|&Yy3o]y'! #ND$ǛjQ;FAqǑf N̅DS͑T~ԶJ&M{l6|Twf+6wmcK$_D'z]0ex Yit="tH|71Ag f#|!ƏOc @a9T1QA|ڞAʓP{Rm$M#vz{¥n>qfgx%oM) -QGß. Jm^JwE[k߸ٱa]+x0}򼏻.w|7V٢"WN=&>£|ӭw-D33Y Uޣ;U犉lcmB=& lj5S ,S'<11EC;7K'x ,0 }7_ aOOIspS>K~·b(j= {LM Y:9~q/Khd\I¾մ-.cow-x]o6&8|=1t.up6 p̬hȨ` Z6%,;,pk~&ݨ$J;tO{ Ƽ| ekbSl!CJ? Jy9siC̝7&R]!*O6}C Xn 't {0KI+ -!Q,ő="Ң?Dm&+Ύ -4MDNBpͻ50@eޒ{WP\bNI&e0D@~u}FԙKLf^l+7UXːƛ [t5+ֆz% 5 JFa|Qll~̫^xWxhKvנqr3Xy]^2d'O7]&2B{+EYG +ukh;"'x(02!+%Qj~?lAX|e;tYAlw/תgѱVܱYBۡW?$,-0'. Յ3<;Q?rO$j"xS@E/^ :h2ss_ -'|8YUe C-x4N"p1!F._]UaI k>'cZ$STQ>oFq\n"[}*[ÇT~5LX-iwHOAl>Akjfx50K{JW\5^)k Z֯m]3/5 - 7>ψ{LɣQAhq0 ơ#ľ1h ๧M#V bo"ZBW37M8?K/~*_L_7u|Pkf>-L Uм8Ȯ '\N՝;EݼD6z.\מ)ʨje]]uu^Z8K j^7.p69L -93 B33h$"1Z#bP^+yzקw'ǣ Ϙ)\򞘿Hizz~fhڔ~$&nbǦCV߾|D7DDNT ܰWʃ8^!@hTH۹{ RqhH\FPX*B{ ɧqz>!0n^^/v6b~pb @oX2rRToUTR^h(_B,|lRF$ -ӃO[o?=aO'K^<7ȊM吞(XC?P<.rB"OcicQ[MbMhժfLg3}f]N(\Vp1rpڶUB86/9v&W8mȠ1l]sy-Ƴr.Ir#xEINď'0x*8\ñ7sտcI#'ӨMDH)P`+/ۓ>?IOÛ["\\&9F 18|rl]:< ͩ拐Făl$eeL ѸD -D LEo>⡺ĚAh&|.'*Tʸ}l +UBumEL/K+\є$9c&U7|YG^+C6$ 1or:%贶9sؗv~9ad뗘Ufn^)2t_ю}c?~l eGGK1ϐg4sr[ -endstream -endobj -352 0 obj -<< -/Length 41 -/Filter /FlateDecode ->> -stream -xk``0j󔊉/,z @f @iTc0t 2 -endstream -endobj -353 0 obj -<< -/Panose () ->> -endobj -354 0 obj -<< -/Length 667 -/Subtype /CIDFontType0C -/Filter /FlateDecode ->> -stream -xmOLP_Y(OeݜD!l@ј-4K:J@8rDFfPÎp2p{`Q;>yɗ$ hn?'b~o;[NPsjM2Ӭ5EhlmZ=ix[!fN:$'&!?qbE P7zAqRt/vB*`f˰=q>'`,=`3oS #(KA~ F „g'VF)xa(&8fHRЭ  S(#"B("4y瓲 1G6>k;::9iJcH"\, -B6@XdH.أj9Bب+:E=}(ϰyQ8F=xދҸZj]1xm~jm՞G0;*+(۸`Qgr6|Qp]VT~ ?o#`̪t~ OxXc(p -٢vs=-5dgeVt - WQޭ.' E -endstream -endobj -355 0 obj -<< -/Length 17 -/Filter /FlateDecode ->> -stream -xk``````b -endstream -endobj -356 0 obj -<< -/Panose () ->> -endobj -357 0 obj -<< -/Length 2710 -/Subtype /CIDFontType0C -/Filter /FlateDecode ->> -stream -xڭW PWaiqbhct9\Ed1QY`91 3c`K9f$bč#$uMLtM^gn&dSnwWw{0ooL$MKa3"W֤ؠY O20ψ)(T&>$~uMh@0Ld~sdȞ4=^&`4$v{ lK485Og̞='TMJH3g3K媭.9Qqfe1 -I4 ĪU)&Y^tjIj i$chUU:uc`ZFfEY˄kX=U̬Y SD>mA`ސ& 1A@wWE&,@]kLZJJ(6U M,,[=-Ŗa/`+K,ŒT, CG1!3|Dv/e/[\]$@JgJc,OϭQzG) p0EësLp0FT&EZh~ev&}E$mt6U%컩pڥ(Hb"ezX|tF*PkۙK]W.c5 }:Qk[:/ɨ|ʤUFeJyT7Ͻq{^wu`'ZFO/6Z&evUN]DK bwQTs6;vsI!`Eg,8Yanj*{U(s5Sv6 EWRJj`wF|-o!7jMIq$hLBZHNh4.j58v4+!tm -6,",4gK[m㠞ƛ=Vr0YCD܇XIZ" -0cv'>uJC].PiާWP.+!)? f[^?FO3i䏯 mTz{뺁2# ~6'rz1oz R6a>=դUFpRS ʶnऄX:FĞ*LE'lͲ,wi8^lLɤU2`J?\ Y>pݑ]oʣ/i/[Ip:+ ʟ!uKO'tAbh.NўQon7 F -6g2'eSgq/F)5{F6 -|g}]%6A[oL^csKm2Gl sſ Q, -endstream -endobj -358 0 obj -<< -/Length 29 -/Filter /FlateDecode ->> -stream -xk``P0Q0P 00] -endstream -endobj -xref -0 359 -0000000000 65535 f -0000000015 00000 n -0000001917 00000 n -0000002050 00000 n -0000002235 00000 n -0000002270 00000 n -0000002343 00000 n -0000000148 00000 n -0000017916 00000 n -0000018122 00000 n -0000018321 00000 n -0000018528 00000 n -0000018728 00000 n -0000018935 00000 n -0000019135 00000 n -0000019321 00000 n -0000019521 00000 n -0000019707 00000 n -0000019893 00000 n -0000020055 00000 n -0000020175 00000 n -0000021765 00000 n -0000021898 00000 n -0000022031 00000 n -0000022195 00000 n -0000022276 00000 n -0000022359 00000 n -0000024214 00000 n -0000024359 00000 n -0000024434 00000 n -0000024567 00000 n -0000024700 00000 n -0000024834 00000 n -0000026080 00000 n -0000026266 00000 n -0000026400 00000 n -0000026534 00000 n -0000030708 00000 n -0000030819 00000 n -0000030953 00000 n -0000031087 00000 n -0000031221 00000 n -0000031355 00000 n -0000031489 00000 n -0000036377 00000 n -0000036488 00000 n -0000036622 00000 n -0000036756 00000 n -0000036890 00000 n -0000037023 00000 n -0000040875 00000 n -0000040986 00000 n -0000041120 00000 n -0000041253 00000 n -0000041387 00000 n -0000041521 00000 n -0000041654 00000 n -0000047761 00000 n -0000047872 00000 n -0000048006 00000 n -0000048140 00000 n -0000048274 00000 n -0000048408 00000 n -0000053839 00000 n -0000053950 00000 n -0000054083 00000 n -0000054216 00000 n -0000059349 00000 n -0000059460 00000 n -0000059593 00000 n -0000059727 00000 n -0000059860 00000 n -0000059993 00000 n -0000063447 00000 n -0000063558 00000 n -0000063692 00000 n -0000063825 00000 n -0000065292 00000 n -0000065403 00000 n -0000065536 00000 n -0000065670 00000 n -0000066779 00000 n -0000066890 00000 n -0000066960 00000 n -0000112805 00000 n -0000117261 00000 n -0000117344 00000 n -0000117414 00000 n -0000117526 00000 n -0000117612 00000 n -0000117658 00000 n -0000117821 00000 n -0000117868 00000 n -0000117994 00000 n -0000118047 00000 n -0000118069 00000 n -0000118091 00000 n -0000118260 00000 n -0000143179 00000 n -0000143263 00000 n -0000143323 00000 n -0000143376 00000 n -0000143453 00000 n -0000165875 00000 n -0000188731 00000 n -0000213735 00000 n -0000239907 00000 n -0000239964 00000 n -0000240023 00000 n -0000240177 00000 n -0000240222 00000 n -0000240271 00000 n -0000240316 00000 n -0000240361 00000 n -0000240406 00000 n -0000240616 00000 n -0000240662 00000 n -0000240709 00000 n -0000240756 00000 n -0000240808 00000 n -0000240990 00000 n -0000241035 00000 n -0000241080 00000 n -0000241132 00000 n -0000241184 00000 n -0000241230 00000 n -0000241544 00000 n -0000241590 00000 n -0000241640 00000 n -0000241685 00000 n -0000241730 00000 n -0000241923 00000 n -0000241969 00000 n -0000242021 00000 n -0000242308 00000 n -0000242355 00000 n -0000242401 00000 n -0000242447 00000 n -0000242493 00000 n -0000242675 00000 n -0000242720 00000 n -0000242765 00000 n -0000242856 00000 n -0000242942 00000 n -0000243028 00000 n -0000243117 00000 n -0000243280 00000 n -0000243446 00000 n -0000243609 00000 n -0000243775 00000 n -0000244849 00000 n -0000245925 00000 n -0000246381 00000 n -0000246461 00000 n -0000246651 00000 n -0000246896 00000 n -0000247086 00000 n -0000247250 00000 n -0000247420 00000 n -0000247612 00000 n -0000247808 00000 n -0000248006 00000 n -0000248104 00000 n -0000248151 00000 n -0000248315 00000 n -0000248403 00000 n -0000248493 00000 n -0000248540 00000 n -0000248663 00000 n -0000249278 00000 n -0000250486 00000 n -0000250814 00000 n -0000251272 00000 n -0000251510 00000 n -0000252242 00000 n -0000252856 00000 n -0000253200 00000 n -0000254630 00000 n -0000255175 00000 n -0000255363 00000 n -0000255697 00000 n -0000256290 00000 n -0000256624 00000 n -0000256958 00000 n -0000257237 00000 n -0000257572 00000 n -0000258379 00000 n -0000259151 00000 n -0000259294 00000 n -0000260123 00000 n -0000260710 00000 n -0000261286 00000 n -0000261576 00000 n -0000261770 00000 n -0000262441 00000 n -0000263074 00000 n -0000263268 00000 n -0000263470 00000 n -0000263611 00000 n -0000263804 00000 n -0000264262 00000 n -0000264456 00000 n -0000264716 00000 n -0000264908 00000 n -0000265309 00000 n -0000265740 00000 n -0000266223 00000 n -0000266794 00000 n -0000267369 00000 n -0000267550 00000 n -0000267881 00000 n -0000268243 00000 n -0000268665 00000 n -0000268744 00000 n -0000268804 00000 n -0000268865 00000 n -0000268926 00000 n -0000268987 00000 n -0000269048 00000 n -0000269109 00000 n -0000269170 00000 n -0000269231 00000 n -0000269290 00000 n -0000269351 00000 n -0000269412 00000 n -0000269473 00000 n -0000269534 00000 n -0000269595 00000 n -0000269656 00000 n -0000269717 00000 n -0000269778 00000 n -0000269839 00000 n -0000269899 00000 n -0000269960 00000 n -0000270021 00000 n -0000270081 00000 n -0000270142 00000 n -0000270203 00000 n -0000270264 00000 n -0000270325 00000 n -0000270386 00000 n -0000270447 00000 n -0000270508 00000 n -0000270569 00000 n -0000270630 00000 n -0000270691 00000 n -0000270752 00000 n -0000270813 00000 n -0000270873 00000 n -0000270933 00000 n -0000270993 00000 n -0000271054 00000 n -0000271115 00000 n -0000271176 00000 n -0000271237 00000 n -0000271298 00000 n -0000271359 00000 n -0000271420 00000 n -0000271481 00000 n -0000271542 00000 n -0000271602 00000 n -0000271663 00000 n -0000271724 00000 n -0000271785 00000 n -0000271832 00000 n -0000271960 00000 n -0000272048 00000 n -0000272143 00000 n -0000272195 00000 n -0000272247 00000 n -0000272294 00000 n -0000272686 00000 n -0000273250 00000 n -0000274169 00000 n -0000274463 00000 n -0000274736 00000 n -0000275018 00000 n -0000275680 00000 n -0000275945 00000 n -0000276207 00000 n -0000277053 00000 n -0000277307 00000 n -0000277652 00000 n -0000277922 00000 n -0000278166 00000 n -0000278714 00000 n -0000279178 00000 n -0000279411 00000 n -0000279684 00000 n -0000280025 00000 n -0000280397 00000 n -0000280633 00000 n -0000281711 00000 n -0000281928 00000 n -0000282227 00000 n -0000282675 00000 n -0000282981 00000 n -0000284084 00000 n -0000284390 00000 n -0000284632 00000 n -0000284891 00000 n -0000285230 00000 n -0000285474 00000 n -0000285699 00000 n -0000285931 00000 n -0000286172 00000 n -0000286414 00000 n -0000286655 00000 n -0000286901 00000 n -0000286977 00000 n -0000287232 00000 n -0000287308 00000 n -0000287560 00000 n -0000287636 00000 n -0000287891 00000 n -0000287967 00000 n -0000288219 00000 n -0000288266 00000 n -0000288318 00000 n -0000288370 00000 n -0000316981 00000 n -0000340310 00000 n -0000362378 00000 n -0000368146 00000 n -0000384904 00000 n -0000412709 00000 n -0000427997 00000 n -0000447215 00000 n -0000482282 00000 n -0000511185 00000 n -0000513576 00000 n -0000530547 00000 n -0000541640 00000 n -0000546952 00000 n -0000549210 00000 n -0000555379 00000 n -0000560162 00000 n -0000582838 00000 n -0000585525 00000 n -0000607547 00000 n -0000627472 00000 n -0000645564 00000 n -0000652822 00000 n -0000672868 00000 n -0000675545 00000 n -0000678290 00000 n -0000681044 00000 n -0000701686 00000 n -0000701732 00000 n -0000705621 00000 n -0000705720 00000 n -0000705766 00000 n -0000711664 00000 n -0000711780 00000 n -0000711826 00000 n -0000712593 00000 n -0000712685 00000 n -0000712731 00000 n -0000715542 00000 n -trailer -<< -/Root 1 0 R -/Info 7 0 R -/ID [<92736C097C7A7FA479482188324FEB6E> <92736C097C7A7FA479482188324FEB6E>] -/Size 359 ->> -startxref -715646 -%%EOF diff --git a/assets/eip-5988/papers/proving_resistance_linear_layer.pdf b/assets/eip-5988/papers/proving_resistance_linear_layer.pdf deleted file mode 100644 index 01b8585..0000000 Binary files a/assets/eip-5988/papers/proving_resistance_linear_layer.pdf and /dev/null differ diff --git a/assets/eip-5988/papers/report_security_stark_friendly_hash.pdf b/assets/eip-5988/papers/report_security_stark_friendly_hash.pdf deleted file mode 100644 index 5f92b90..0000000 Binary files a/assets/eip-5988/papers/report_security_stark_friendly_hash.pdf and /dev/null differ diff --git a/assets/eip-5988/papers/security_poseidon_non_binary_differential_attacks.pdf b/assets/eip-5988/papers/security_poseidon_non_binary_differential_attacks.pdf deleted file mode 100644 index 43785ad..0000000 --- a/assets/eip-5988/papers/security_poseidon_non_binary_differential_attacks.pdf +++ /dev/null @@ -1,1201 +0,0 @@ -%PDF-1.4 % -99 0 obj <> endobj 97 0 obj <>stream - - - - - PScript5.dll Version 5.2.2 - 2021-04-06T18:13:35+08:00 - 2021-03-27T10:47:18+02:00 - 2021-04-06T18:13:35+08:00 - - - Acrobat Distiller 9.0.0 (Windows) - - - application/pdf - - - - - - - - - - - - - uuid:dbfcdf8a-1af9-4e6b-93fa-a5765ca12de8 - uuid:949c9217-fff9-4d28-839a-c30dd444fc02 - default - 1 - - - - converted - uuid:c82f284b-a63c-483f-b289-fa3fad246c64 - converted to PDF/A-2b - pdfToolbox - 2021-04-01T07:51:59+08:00 - - - - - - 2 - B - - - - - - http://ns.adobe.com/pdf/1.3/ - pdf - Adobe PDF Schema - - - - internal - A name object indicating whether the document has been modified to include trapping information - Trapped - Text - - - - - - http://ns.adobe.com/xap/1.0/mm/ - xmpMM - XMP Media Management Schema - - - - internal - UUID based identifier for specific incarnation of a document - InstanceID - URI - - - internal - The common identifier for all versions and renditions of a document. - OriginalDocumentID - URI - - - - - - http://www.aiim.org/pdfa/ns/id/ - pdfaid - PDF/A ID Schema - - - - internal - Part of PDF/A standard - part - Integer - - - internal - Amendment of PDF/A standard - amd - Text - - - internal - Conformance level of PDF/A standard - conformance - Text - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -endstream endobj 50 0 obj <> endobj 94 0 obj [95 0 R] endobj 77 0 obj <> endobj 78 0 obj <> endobj 79 0 obj <> endobj 29 0 obj <> endobj 30 0 obj <>stream -xZmSFp|J+Vh^%*u'N⻔riwVAH{pEu%-LOOOv?{GL4zdew߿N{G>۟qoY0~(_x~wtZeTl~ERg|E .wlQde6 `!e#(@! `"`1K&^`+l7tۍ|.qg&7 #_H~ё`5iX$| -w0$|g}"| @@x'd;& -E1.Gas? 3?qYL6nvoVu.uZ,_ܸnt&)f.Ҥ2ի4^4U:}0g@-ܟ1_ϿOߒMf%i]Y,:&WI41/ōHNM]d#<5' ɜ-J9EndwV3g.ǣQDxu2Llpj__:8|x_痧WEOda#TOO͠u]oWGG"I8QF31ӒXg"z}8#b{) G~cAUmJ L0OTJ>$ەɓG;+ygџёIX&hTaLݚ9cI ؒxX)-%Dp&5$YMpΝ_EyW &&JEʻ:>^9x -OZDdXeLDd`;p3c13?c-dU?)1wf0?~M'C'/dVP\@Ko==y1Qti$i~d21("_XV*?\[WudYӥ+-  P&Mor5XygYa *r[b*ߜ9=kA&ULBj^&y)(!o?c$ -bDެ9+flIܟPRͲ.<]lguc9I_\徯)l!gZfJZq7* B>3.Eb~ -MӐ|AJ+XkH,4$D͞] MZ@(8}ve ٔigq;5-}@u-̣e%z962X2CbBEҼgCˣCABߘ}$8>O0,P6 ɐONjqQf3/f򧋤+Z - E1#dzc e@ )^mjY=`Q)t')XEL΂2!gP“2]C~wC"1C5JgHorՎ$A+5L;y1Up8eې?[@eѕIOPs14;f!q%rq(loe*hJǚ f׮5'o)[ȹTEUu{b1*vNQR”_ 7 5";Y4M -崏=.MR0 )=jL IDLnwO -.T yW3o ,wwFtW éov,nȝt뫳?HaiJ9!hF'Ž9܍7Z~ƁmEM݃gW&Eų3w0t4mq 8"oH -Iz?[".-”QOPżHFAf \vG"lѨ>$Aρ l9i-!}w\ހp9rq;^FNm hW#'bE 1wgNDCfO+&^o++DeQ~Ib5O^iI+CK@2 mlH[tp@jIK.j:,+UA#D#~@2i,Yy8/hW->/[I_2sW& A[s;Z3ia3d̕(Bamx8tF(Vfq -'l#!rN.zw*w޼Wxa^ه6h URF_Mpi\ߕI*3tWBzTP[T`Gb2H(,-4{";8őPw߮Q5ZoпX}(OxAQ_ЁߨlϽ4g -endstream endobj 31 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>> endobj 91 0 obj <>stream -HW  >,"3S2І @*r* Ov?7#,6=;4N,CWS,~l>kql,2gr,!|7z1< ;wsz .鬨(+sG}W}}6jmk^0Y(A>A^GK>M[]r3I25e֪q 󣨎b$ sdbno-'4ۨi܈GJ[휵8ȝ(4 -#JX - -8ӦsAQ-5EZe\Pvr#6 Q7jOֹ|OPu_[x9=V5އ]֐:^ -]/,,,,,,5_6-b, -endstream endobj 92 0 obj [/Indexed/DeviceRGB 255 93 0 R] endobj 93 0 obj <>stream -HR[BA ,`X XB,` dz^|qc -endstream endobj 127 0 obj <> endobj 118 0 obj <> endobj 36 0 obj <> endobj 37 0 obj <> endobj 38 0 obj <> endobj 39 0 obj <>stream -H?o@R83ؑ1EUhE'T BΎ]@qs;[6| -$6&&&$.6C3}^<:{|{ճ+mGx>has4Q!B(~2~|G?>auw%| ?bT$O4)YH\\|\;HFk:2Vv߆UämX{UN$1x̌35[8#,.L3Cc&b*1N}!xUnmJ.ˌ1JTz+C+B>V}KÁcYeM*4b\XB5%''4VRO5NdnМu`w}kīƭ.h?Kc] %[k;uU7O{L㼔tN7Z -endstream endobj 119 0 obj <> endobj 120 0 obj <>stream -H$S[L[u.N!䃾de6I]017[.Ai{zzz!tlMLNI%×~U׹.4I87/o2o2CGC7]{ǙL[fF21U1d*Aw9);Hxgg -ZZV OJI}sz@*sK?uߠtH?1/|ޘ#G-DWq eJ$kKxi2Ħ_pO0^x2qj"h^xi_PVf% zzs2e 9f1ӌ .vG8#̗E׍`ahUHtaبPL"n`1ۂ A !I"-=_8<+xP"$tx{#DD9BSJWM/60<:q~V]R1i5]{wY:\cwnvZ.B~ bD&0-y^Gl(6z}c:a}lGmfWh2dM&=cYD;|UDžmaskD[H0^]XmFF2*@zCpgwa=o=nۙ+xW,\Ju&toNV/8Qw|v ]uZՌ+t.D8zĢۊ//XBR雾x{M-.DYD6"b@+C.QX`. T0H -tDi_|5tHg63m] N\;z9!*ʩ6CNa;=sKֹn4E>NusJsC74a:AXW-(8}NPA`DNaQ1CM!C&{{ ~h>Bqnz?G|{ -endstream endobj 128 0 obj <> endobj 129 0 obj <>stream -HTTilxw\`9wIB!zPAN 9Ż;ޖsqHICU5Q&TTBjl-'=={z߫VT҆MNn?!4t}mo -H-\S _[ $-6"uU-P]|o*Von8qyPOp@Pzxj {%^  tG@XYW͕+X*Bn)QV\/V_TW?Zmhj%Tnmt_lņGRMo9Ub&\3^˚Eh߬}#rк'OgrhKٳ>ںێmKnuss:OK6HӺv hVv+4) hMi8' q1*n0-8h1%Qp_V>ݥ[@K7 rT^0O@K3Ha'IЕ&Эչq{J$] ֦*jC88i R:M%2`"G3n" --fSL~cP:N?.ݑj̛nc@֩@ -W!w1!uN&tӹxh)Aܒ} /Y辍-EoC>M:q@oցu[GAQgC=*ۈ0.^ūXRP5 9Ewԉl=:mQl݃x6]Nw"tߵ8S-o,MDIS3]) 404a4J 2{t -q`.o EÑh*r$HrY(Sl.;/jer g3^Á8q6eQcDtuz![ NE?F*eC#zb1i0GvO;QaP KH&@FKj팚"(9C ~fyGygӖaˋk<{|%_+, M\n$c&7}aqz\6rd `ܡ(Jxq0iE.(~EW|sBX*_*E:aHmP!j#8nuadqιj"" -F`ɋBqϩX>U%>~oN(7_ǚk_8 4);dPko}ᓯ٤?%ex$FG˞Fre0rǎMeSY4z\g@U c qxK -?jmQPGdܬ' xl(1 IhOgf!aYoF|v1mF7E%K煍3UH_.|.|-u#y/ *=bzD"8>q%z1oBd7@Yhۏ7ݻ -ES! $Os41Pr}(g&.ela -ߘ3\BSR@}GYXR&؞vՆ49|>Gr&9iF(){  xr>ˇ|!Xeh6ǏMBYWΞAKHϫ9ըI5l -r,"8Ax^@Z-m&YygWzpw˥=pwWD{7 KUY$Y:MڹtZt~ןmɯ5׼u>|q-}EaƈYACW+5HF+ p0C z)r4f3,<=.B!<9Z/cz;.94 vdý!T2J (v_X.󳆮"Z97"'ڛbʰ - VyA4HTBB'"-v =N؎/m#^;JQ+Z0/lc[k-B nՀÓgF]Il#\3zv -;vA\%jZU*.V뵍EzyQEL[L-z^it0F=jc6e=c5њN5pjQAbb4&==؃ISsK aN$Q$4o.{(͐JCHC顳;_Rg9}&.[h(ޟq>dؑOäD(^$~ &~d5n [;ń֫4#!W j rԣh9(n -+V "ڤL^4%Kn%'oy+CQW€Fl"U$YZwo ֈ61>N  v9eL %ql?>}OrƷIlh# ~ p0=!2(*LE3\M\ծ,"vs٩4#StE`b࣑XM -<~VZ9( -PyouGj>TCO'qi^wGxc"RcBRmV5%:\332ԈT\ \Jk XIBY0V6^"$^$ -Etپ0^+'`zB?t_o<1_J/,%pdM$Aj|d:1,5ERkKbp w@u7ݍ PQb85j,'cݹ>\hq6v=%,--o./ӧS}rN};6^nm΃kwujz& "rD4�[[_.sd!S2 'QMM90ߝ9sS?9}?r{\W>*oAU|1-|˘Vw; <Q+iQb "WA[͟|q:Fr%Rk؇}?60 Ҩx* EZ DN( TKq"q+@IaF!ĻIO9ƫS| 5)bI"JLB5Zb0Kx )[؄)/()Ba'1ɪ4 J6#6\àcBFXȆx9*Jq 8><{$ 5UۦqEW{rDZռG 2z,+/v׆x ++ULl(p\ȧU 9?fC̈́ Ǭ.涹 +hA^Րy9Bz?Ћ)_n׉ S޺u^T6ƤP@$r{^! 2a!"l8~%/j| -Y`;aFA^N#G$(@ѝê=V#CVOY]m~UWya@`~ @n@vPg#A^VyJ::"<0[Fx' Y.n*݌N^:G+ tF`ԍSC4'+ΌŒK0), zD[2qQ,gr{r _OU/VOm*!ld K'lY*Mf3ŤUY|ړ$?\W[p.Hfz1w;:MK0(yHp bhƒjHZJ$jY廉K!a`i&3}Hc_]_Q aus;|;}S_gVD%ƒw|{nQs l\)%!f)ڪxH(A# a<,Z+w35!)OE1/N3&%Ą`}18 8=ښeAeYc6o7rk<+a0I$XoiqٿFDu)a E)\k -(-k]TtlT 稪i3 *fH"EDSĂ<5°E3J4^(*9cn~8mhɓ((Ň#d^Ba /ltD0M>qy bRXIf(u:xI00+)*Phbk퍟5~," -@ .&96]5Oγkv= q[6V |tkcfnoܸp7},XxBA9zғsuiخ63fFmA, $nN8pbSBQ܆lÈpr8.)Jɲz\N-F>}XGD9ڧVz2*%zwlX\ˢ;3q#F)Q0`C! -%g8w :NVWsj%t5.b]½񣋏ֱCZFsW6B a5aPQ8#y8C^eLڡ2T)ZpON\l -ks_[!%JF\\ݍm{l_fBaK8\>$|.˒9QesxNs agQ Yrft|"L,#e4E մzn9w){qGe3+'&sS>7/_Xk`+BkFERKtA) OiY r(]_hvW=0`,ώ,4XAĆoVpS!~MO|O3I*M7n:2Oj+T|"1鬊\JޗإfZF*QNgp5$'zcsRXfQ1Tfu;V/KKE|In-+ؤPMF5:*^ra*{ 6yRJR -.fK*@'l6=q :+3.a h2ZH ò$ F1XiiE[GdNM=\AuF9/&@P$)vSdˤhd -Cᘬ)A8s`>F`}07EzIQ59;3AL2M$CHM3d蚘ѡ5rOإ 4MRVU1d |~u56+XÇoܡ4%G$yI UD'R9^H3Ňp{z &sĎ[3!f%2"( -w|DHUQx#m8r%'!U[sخ̓+O޿7]D} h"9rs M9&䔄{//}óQUJ/T* M.VX)P8aZ. -rhFacע-|WV%̈́h,-}eDg*ߙQ. ,K뮩 rfERth.:8Nh16YO ?RP,{9pO??=2DZHH5Ө{|{{s@+6Bx@q!} ~X(/#G~!KeDQԛ*0Q} OB#E*Nŕh[.+E(7zȚ^;]xr1y#^2?Cq ~1zw Sw[Vm_m_ⵍePQ ƣgxty<ٺVUy3Kԯ= q{C˛+eZ\ZO;_'^ };a]j&ՄN޷Քx+>SœT(!)*r-պ+X;Wje4#ʳgE4=ٷ=`ĐrČ9q<4ExmaU0cKEذ1D -BUB*!x*RM&Kv.{a(1XOWZEL2 tm~ܵbv͔u -)oTTN,?:ⱆ J"$+~Ɯj*x>*IH@}5d{̢"V% eu˹"rQEg'Gvq#zܗnv&er4 .tI%ث*J"5,$),Z;K,DvznuՑ4Dad8hacv-g1bCa@] /yw6 #lUY-zV_B,h2|?0C< QXTT NOb> B  -o[`H :6 -P [tTպkFM&4Q8 ?5Dw[f๠lY6vrqe |vh|2EQ,S% -ݨam6QM*XYf9pPxv(ml kW6fKbX -) -P 0umgƃ"|*8s[ X>Cv~ Qe 1uW M"jTV3hZ%"(7elaVh{!> endobj 130 0 obj [/ICCBased 131 0 R] endobj 131 0 obj <>stream -hޜwTTϽwz0z.0. Qf Ml@DEHb!(`HPb0dFJ|yyǽgs{.$O./ 'z8WGбx0Y驾A@$/7z HeOOҬT_lN:K"N3"$F/JPrb[䥟}Qd[Sl1x{#bG\NoX3I[ql2$ 8xtrp/8 pCfq.Knjm͠{r28?.)ɩL^6g,qm"[Z[Z~Q7%" -3R`̊j[~: w!$E}kyhyRm333: }=#vʉe -tqX)I)B>== <8Xȉ9yP:8p΍Lg kk Ѐ$t!0V87`ɀ2A. -@JPA#h'@8 .: ``a!2D!UH 2 dA>P ECqB**Z:]B=h~L2  5pN:|ó@ QC !H,G6 H9R ]H/r Aw( Q(OTJCm@*QGQ-(j MF+ 6h/*t:].G7Зw7 Xa<1:L1s3bXyeb~19 vGĩp+5qy^ oó|= ?'Htv`Ba3BDxHxE$Չ"XAP44077&9$An0;T2421t.54ld+s;# V]=iY9FgM֚k&=%Ō:nc1gcbcfX.}lGv{c)LŖN퉛w/p+/<j$.$%&㒣OdxTԂԑ4i3|o~C:&S@L u[Uo3C3OfIgwdO|;W-wsz 17jl8c͉̈́3+{%lKWr[ $ -llGmnacOkE&EEY׾2⫅;K,KhtiN=e²{^-_V^Oo§s]?TWީrjVQ=w}`嚢zԶiו8>k׍ - E  [ly邟~_Y53rW򯎼^{7so}x>|쇊z>yz -endstream endobj 100 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>>/Rotate 0/Thumb 81 0 R/Type/Page>> endobj 1 0 obj <> endobj 4 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]/XObject<>>>/Rotate 0/Thumb 83 0 R/Type/Page>> endobj 12 0 obj <> endobj 14 0 obj <> endobj 17 0 obj <> endobj 19 0 obj <> endobj 22 0 obj <> endobj 24 0 obj <> endobj 26 0 obj <> endobj 27 0 obj <>stream -xZmsFڪ|ѯGp#'YdmG+*ۓ\W 4$[{=`^\~}o'KYgrbV'7+>6'/Oͮ7'sy3uw#?=>$nbB뇓yͲD&+Ou{:qg sڝX%'SֲU<ƹ'j]l<)ow];y`9j~W~G~ - p'o,U,@h xny?(`\ݥiJI١OHgnӼlNf W,egy7_囍e(~ULkضiv@ Ɖ`10nJg14׏CChX4ޭLx 룉zZɏ˼_6< mxѶ^w6tf,ۑk6EUֵUeNA;*6ةEYKZlN_j!/ߛPS_3/OΪ>F+}ɴ9YWecK*,TߩaS1$_As8)u^nAA`ؽ,)72m57|4'f-v>}gib -D{1 -% [% zu^P3 .FZu/d5{ü7LSWED&˃W?V֚ *Ѣ8Ƌ?T3mp5닗O -6,qxZ"Cbs^ϯy1=3 %`(fP]/X1#R%n0/J('bX|u8$0"8^0l.!.tJx;ܢ/Ew`>\7'AC$U u ǡ#a\,Ql6lB]f{ ȱXO`8MXXblBR `;URE3@C_~3S-;5GS|3[ȟeZ~bT9}Cz]Tߌ6IAiׁ .hZm!AQa8AQ`<调1hlmQa]MS}CNP0.L_wo*~Ɨ\LiǬ:dJȢ g YfR@23]!K"FBjެQ/Hٹf$nEXv3$.,:c":b~>o)EPA>#)bLbTkt ʳK&GS -C^nfZSM1~6P/>}!OUS>\kZnk_ݓ0FUA ;L5sxR)>@FtMt@mTj%&UOcZ1ƾ{ ') -`n4EChYuOTqKC {SY @oiIeM7<3Ӓ e{Bk`|[ЌǼ<=4~:&"C:h@%5hfvC"v:M{raȀԳӾuA_\F;omxY#/fhuAh#nP≜ga0gB B\;' -s@Ql,eir|͋s k!pvž $3;Nlh؅f{Yx)/%- J#o+={g5"AX јbVSA>!W afVLbIwjvć2&* -VM%bEݫ|i1̰K3R}'m<4IGtCk"{7,{1jsRZl+M:(Ke; XpK1.)B$Dr7zuTWEX?&wx^ӽM?8_ -xE"N\]p~QT,v ]h! x(RU4Uy ?+M<"DmH>`7&nmCϴY=U?Gy/+Әj`8d<($  -ݰv}մHnLnz3*JW ٣tb8Փί9тR[vPv;,=]w/Ӯm͋RqYVvKsqoɺp=H ֍1I}}S?Zranʺ.м^d1i3:y"R%UƁ%/ܿq-鬕D,6hBys a U.ahN'WwE3;…DM}XC}/d4ទ/.F!,#~{Oה7B -p L&Bz)SZ;۹lm݇թC *(zNEs@yv7*'K윙IDQA,{P6u{޹NܞnUߗb]uYWнdQL'o 3NTT.Wbq_=6_@e^Vxa -@@A_̚tOE%BEtI (l^/[/..]e:t:d&2^r'L2CZWp6cMuً,c|ne! M6A]d{cAGvD\Tрd^\] рq?aз}EOl(ך0 ܵ0?"~GtehE^B@fC_ђu~lL|;`pYxo~z*>~m=|E}~),aeŞY =E\pY+ w?Vor_ٖ46wK7 02l].$gTsoDi\*Yݡ!Ѿe8;J'M `5?/p5-ԗJul7vvtT:9~c^z+F,. U6eզ}:NS>'|4'?Nd#!@Vo/|ngYWf/B\\1FW*(thq`/:X1[;>u:(/#<9.艿`JW0setjNNk/O =S{XM?Hf Vy$ppwvQ0akG>+,spQs@J+MP~ew駔HhJk# b_P"=lmNfP>x/,w[- 1 18&{.WXsPT+8c+U_BГ&*E3efiI837݌ +7jBl| 96oHZ3tRr-(' }9۳W+|4wɷ1mA-MǗMԼȏ-lfL#%Ƣ7J95 ZAMp͘zGuzu3AU( /4?>4 A^A(B .O~sN -endstream endobj 28 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>> endobj 90 0 obj <>stream -HW  >,:ө@n-1.Kyk^<ij#iAnNU4^-ۇN9۞Ol"ONJ} b` z3g'@DQre@\k!iAMF 4ZuuWy:3#A:+y{'hDUqpjآZp㴠A:?r/$T~TT_hq%p!0J9eڸMワ06NKЍnbq;Wz{wsP]QiDDŞydwst*:@qP N*\5e2JޤH͌O2gM4()9q`GS -endstream endobj 121 0 obj <> endobj 124 0 obj <> endobj 32 0 obj <> endobj 41 0 obj <> endobj 42 0 obj <> endobj 43 0 obj <> endobj 44 0 obj <>stream -HL[l#gǝ;`ZM4_77ހ[$i!2[mN@B$H -e(B2aYThn:mV&䳘"+ZeCе`u0̓$ |>D<5TV"̌~>So}!ͤ 3a2, RuR\,W -<r|.ɷ|;l;$#r;a&Ʉ4d0h"*sH.]༐Nhħ`W>lͻWJM ;kC`v?Rٟ_#_|m@;hU e=eRp޺c!+BZ6 + ݮ eYPFfҙUMyތ/&పٔv"^ϗ^դԪx=0Ch"Wt7Hj%H"&aS8yRƻ; %Įk[l :hv8Ђ߾wQ7pNQ1%K,-ڞ>R :߂{ݰw S k58z? uQ,IN3T(m#7J!v-ڄ4gD;J<~pFĦEjRyXB"C+ ‚~ 6!"#VGkZ>^h}7r"6fi,0u$81c=fq> endobj 34 0 obj <> endobj 35 0 obj <>stream -HUyp@ڶ:,<+PhL2|C(&Edb[m˲lYZL,i%6>!$m:SiC54L|},|⢢u{5몓Ͼ{\*,Ϭ 7 TwRV>LI<慗mm;wWV7ӌtEV"=7GZȐf+EPdf)22Hr*sQ(2L$' -BHdʤ=IBir( EN-j*>kh x/7/ǖ/U- ,}(Y0(!xұ-X>1/[Orgxn/3cRX8!ɱCJe\TKNB=ɡd@;y.,n+ol j UvmEq*oX5$FhƝ# O<~h2O{|>I(u p$\,!p@Eb#9#~`J9lԞ׎#<.]DV^X$*)WZz\Fpt̙+b/{" q{I9,UX:j$5ͭMdQܺ4W[>D4 MrX\TJ*Dfd^H_urF,_NdTx {P/F9pt][Ai9j$1 y Oڠ$jn}mhT$vV ) !Wo s^k% C֨ -}.7ruY.d׷S:@:Cހ't{ĬfAH:f2cn "KeǠ]fXL6t; ݯZg )i6lM(,Qb@wQ ]cV}[G^fV$fvEbv\nviuz3b]Zwɡp5LNBZ|ew;鶳bkJG% D9Ns (.BҰ}X2& ]. BsauB? MB pp ->'øx:% ;$l\ؖ"ZO%ermmCMck $UBlN%|8=<~sKV ?ݪkF~5D\~OŰbq-U9>Y$LHAN -˰T%pFu#Lw*: Bý7"[n; Q韓͛'q{7K=<=<=q3o&#c;(5UEB"h,Y+O=Fgk|=wJcʦN|\qu;=$92?Vt/ -{嚇M򣢛[nn$VXB , -~ܴnɭzd瑝^Wji6ӷ?狿}HDRLD(:P"ŊŮ(E]q蛮g\?OK@Qȧܬ7}K_P NN]qJrL}BRZ޸K{Ŀ79&0w -YoXT-_}J# -endstream endobj 125 0 obj <> endobj 126 0 obj <>stream -HlYp՟N:%I; t&B+js%oɲ-cKZ!iWlK| a'itJ4N;v3S)O>7VA5PUUrCǎzCb8b9Qz~_\ymAw@M%t%G)OZS+%[O'T6 -A/5[ 2P)UP -UUUVckAGvRzM ]uͯk_lنs7n$_# _^m?oZ$>.b05.K+V_-PfE.I@*GHt02C A7B-l/F2Ja&GQ9 &Z݂u4mM;a -CT|&b&pr8 -&:a Q䜸 #HLsƲi2*/W*W/ɛua^c(k)x"8AR82DHl#a/}ϋbLQڽua`qr7o 3 s1nY_m[+l/-7ЙB1;3&N 8mLo@.xaGzC ~ -38EL`C2%grtbx#D4X,mS=#s bѥ)]Fm2YԶm˜ւnoDkhN׿kxt5OH40hePGCkJtSwPB -O rݱ=u$bL +ߖޔ=X}vIKo9j{H،;*30z:-I' I"N3ĉ$H %D_5iJR~L(TZ45G,A;(uψK~yfh3jzNS_d -X&Πڦ6y 4`lH;ޑN[UGOO>+XjH3ʁsf\(U\(RqُMXec qxХ$ >V|STJ,+ybVX[q`Ne+s+B -Qm6p˄$(ꭴľ eǔB7\5f՗6IG#r'LgD_il#.ֽqt\Os|$Fhb`/πSZ.ѺS3"R:&Cf3y5r46ub ,?Fyx2&dQj8;B>1oV{;)|5f@Q=jccFCHo'3ųt .Dp#`^4Ӏ6uDNRRR+r{M1?(/2TrO֖F;5 ԡOOL.?=ww ܃_cw_; >`>)A -I12s`|V.apz)+ eʖso_.{ͷ{Mup=rs6Y:^krv;Z<RWdդ\;d`}T| ޹"̏nOw%|YHC1R B"W_~/&=Q'i7DQ鈼m`a?@LVrϏq6|H,QQ2xJOW/\C1l R~nGT$ngi{uQuQ)qqLOFI)Mdr<2 R;\/h Qt6E*!^&ziv@@6Dld4FF*'7YF%=#<Ψ3H $PiJ ThUh\v{el~Ywg|'7x7&=-Rx(۲H)JUB%a?NHxsw3| -:!@;Cs( ;}nuKo]sӰvr -H>Waqh!KɈƻ}r'"#@òO!Pp 3ї:H<\Yar]`xmBR-i"._VID #2l˕|)bBgaH !ꃷ*wc$mm6,t!ZtQKς -.%VYB+XSB~!_ɭf-7& i3oaov=~Rk~bfy rXfΔ%GWl:'WU-,<"mP $^ϔnٷ^7VzͨCg -`4.{EF`EH#Hji\5C}ڤNb|/9¤t{}^flF_A'`QH,=vi]@M`n컻1?[<}oM6uV?؈㕃#?':?|1mmgb}+'Z"]0yOuBɱee| :vEYLaZ*&r- 8l Rh(+.x"# . ١b><619Gh+}5n~\BI*\~Br{8ٍz,ke&ŢLKb [QiS9uuc^d?qd`sy9Vq>d=Q2l{"taYJxYg%8]- W6?՗@;[vGth"Z'b/d%MKhojT=E-7,N.ϛW[ }m=S <[Z@'X'`+*MGY -g4 @R4FS,_,MdrxŸqnfP,%sRTA<"5% Ihsx<^{o^(ݖ4HJT !^yFEgHsw Vw]pgGD;Y'qX5,r,^ʖrj-(jAW,313M|~jmmޒ ITD?"#"IQR}P -Qʾg~.At/'}%rv:3 ޺:$&lo>X8K/ᗪ %C$՘":Խ;~lFFNCX?pF!,5TY( qQpi5 RB^t&C1T.`fmMW;`}yMd$;BRު]٘B[k-4Fz[.E'hbao\|60wǷwo7[bg{n~aWq!i'' %ꝆIS~vժ!AYl;bzO8dYi nlݝ#Dh -c1Y)+[.cJQ/YxQ/jfkY$A̋2!_z+ab >UclVO&&،pj)FqhjnQX tVdP2: @x-gߣJ'3{p޸9>&s~f0z~Uڅ1qc1Ǡ49) SWJZM7)eFe8 ܊jm`w0G> \ -[!*aY.2F͙B&I߅B[I5`[~|q[zV;:$։xP@ ctx|D\f -Ėu;N0& F7._!kϱ?Hn  ҕυ8`KQrZis2a:Cb,ͧ4Y@&Rh)+& -D01%R6\^2rf)xbZZ¨ P vf* -.U]1ӤɼqɤIkMZv#(.W.\xՋ}|߽||T=xL[M%5Gw$U > Lm$xRRQCUWZjEe~X2x -DQK0Δ\(nЎ5-RRTN$#s %di;nآ[-7ё#Jn3zg! +ԃsr"Y%=i;}6{,<v]:6լuWut mxyP3.KA\pX}EK decpe8X|2+oŏSdf*LIBE\g>zKغ2G m怋p5+BٲA# t'JK&g },؇R\=mL]tNSL~>óL4\Mzâ -uEQTNcWCop~G>}=j[k.*X,Ӡu2tpk Nl #j'\:bh&nV)zX6{AܣtOٔ$Z$KUq?cC`yS[q)57+a (x2 y"2[O>TӵﴗeErexnOpU|)7)&TEbދEs~[kVpe6_-cx-y>::h=X -endstream endobj 122 0 obj <> endobj 123 0 obj <>stream -H|kPW]_ȁ]IMֵءc*=[@Aj]"! 0 #S8דM~3$1Kg3/9Ϲ"hM*$.>NAu!WʗՕI͕)j5VWFLH!Zؘo~ؼ\΄en;i5? -hÎR~-:},:*: R$*5ʂD)H -mAo@vs#zv@At>SAvg*T]X;NQ2)cRY+k<^/Izl/mOra SnڻW|mE<^oIef,/eoٷ.! -vfl[YiY&>EKX.?gP;zd=u3^n?gF-*5JFR-HkH%bq'8- 18L^grn4pczg">; -],ԘzdǍǔGqѺ<əK5j-0 -`LJF%vip ùs0ڬ&ͪ:99U Pݲ790 -x,1 " :fof-Ғ"~u|ѨG[\&ʆS=Фntz~HDNnI OzPk#q4bK؂=3pZ?ć7 M0Si@z -v^T*%TtpOFpǦcͲAbP^.GvI$Ҳ'޳]鏸`HZRu%|Ky+^LnKfÇMb>߇%_@ZJȑ}0;o6dH"9KVj^TVSԄgc8q( -> t[ao^h%v6>Cc]E,1T䌂\ -z}==?3?{kNdn->fÞe,U)c WOи)uY4eP |8yY+;Tvu5+nPdzGށxgnEogqdUw -XUq8^QW[ V#3Ղ{t<&?EdoaǏƋGoGn[|rog/ bA6U>X -S)'yk!}Yܠ0Deem8P7Im,a"E"==Ѯ(9Z8clhI2{ŧWfVyւ:Mn+1SV`[f<0Dns=8t>yYinv36e*ʂ{. |\F7?2~#I$c|]M|OW`-Ҷڹ g0F٫!Q]M)QP^_ ߝDOt%d$c4A+.փbC.Vx)#{j@ӔX\^FƬIREv8It,'aOk|>@᭰ -!Cm^ irIYDN2ئlQJYqɶFn28&Ix+,ɾ%Cz)` Fw@: !pR` :ppf7r:ZIRWt3$̈́'GxFݝ]~4d 4Nd *{c 6{lP^g_\(QM'pu -dW[lEWHQ -$OUJa݆AV`u@ݷƹN"YWB&M"Rwɶ,ɖ[6dɒ +ݐb+mQ[Vsw)rxd>nDTWGb%mjfaH52 Yi>LRV5 !1.J 0^dff5ÄYhՍ2 BSSԇt)oHJQbXXff;+(`lA-H5{ZEBbaa+[<^bE:BK)OOg{#IjB CP<g𪆙-fKN\EwNO WL#B>1.8;lD\Dt1YFa2㓕*qXl=Eԍ*p1% bO|No-]t 'o88~j vB Lhn߱^!D!MevE-kÇ9s1C{B^<8E\ט˘B o:|:d%&^0mh"QȗvJ/t # h0:fʌ}췅:60ʖζ; Zk RK.;~ܗ:euגAM*v$؅r& h $/ٖZBK}|lxR Z-'GI35%'RZxO[ZؓKbg|x3,蚡R]sVQv.:Zܚ7GNN7s].2U<޺q3[v[\k W=w+Vb0|(]iU]V13+:Y, 0\!k T:6=τCD(B|h@Cx!=}D*YzH/N,= 4y&q,Lʙ(UK"qG@si%jL}cMXϐn]ZQN)~J4)ncsgSkoO#48*BF%YI+BJm]I"O']?ȆE6WI"ʵvчOw\.Vt(0fTW#Pp ߯]oSGѠJ\zvѻē?o>Ly<V~z=?V_'ꁜ&z!><;Os]b/ ݬ#w5A51I/%O6۹mb{WZ'06;K9x{ܿ7g#$xü6Yh_{}Pyo?8vJc{->^! jp*J%$K ưrJ&ɚUu= ̊AuI`TftK}3.ĥKx`vFCoz{}`BEEYHU۶}DZar$ 6` -ksMv@2l9Yvbٺ%J$E>xvݹ woX[HX0{֤AO2EBRd胞!Ȩ’KELŻUAUPY%LϾ6v6o਒*lߺ}/G{Y"h8T$фۤ;HcU.7Wnm맧DFv7Eu^ڽ{ ܵKT1HQtan" y5I rSVvkU [F^G53l0 `[z2W=gF.g?^Ϟ \Zh2$]PU3$Ѵ"NX8C9d[X.Ђ(/)e˚ٺU1kc6-2G+#g^ ,Uku7]gnlZokH(p - pi(fDXj\R%AֈT -%T$,xrS @ϼ%+#jZd~Wa' /̊¡ -ojm)M88䤵T `Md=Enb6_e, -FlJ!t Eђvufq\Ūzb:MHiIuy$p`Hq4q@=!rV¶ۄ+I(i.*}w Py:֦E>WƌLp~=ן0, @kVݭ5/gSD2g bR!=2L܄mS%eAB%/H>''‘PÓco1*,^Hr{tAeUࠌH#\~i袧~p=cފuL9PyΣtG2uެzuSx.V x_$BIVT+u'φC1' ILOZ8̳b!Տ^[أ'wwjM KE-C%^"J́l4Osx' <ڲXT -8 07xÄ=Dr,pa/\ Jq8B$T)77ОpSʃXypH*f=^oTwv:5pjrZ|8 8l }{vBl˘3 8UHr BjC -hqYU0m4!d,KȚxr/Ȫ WՍ]􏽟ewY/+PVE>(+ D%ŏCFJlЋjan#!ddĵtf:_ tfޟ[QƬrwAw'>PEPN -a*P複 -.@̂Q ?- j~8DndT! \ڼ62&Wd(,DsЅ"9OE^p*( +2.-XP?OG̕ƈqdln/`q}>@aY?qu1jcJ` ~ueײUІRZ (xv -z+q,{m6;mwib+3Йs'S#8Q@y 5?1B'( A/@Yj"* Ó>u]rT@ĔvϿ60qYaNfUkN^4.oAƖ 8P+G($LN٩hn>8}?xz.6(Klʉ gF)DKP4%&kgc=<ڳ/q4Kt%* pAJHȅkŤ2f}~qcJ5KBIY'(@d6˪ )I% Ģ$\!> g\n[̙Щރj&i >+ 9Y&MxK0{ɷث6N/ ,i!A -8^z;T%4s0;1Be8pO D,SM#춍jU2Z]Vjmfgf; ttʺs sb"ԢSh_w% f"*N8uM]hjٺS%L^۪ӡikH$GҚyh֜k#o'vPm_6jPZ 8{+hT('oԘP|<> ]#`9V58{$ArMmZvWFLL!Ũ ukVCzLR$WdT^Eeꗽ+kM2k@8&ic h=-T]60RDtwwW[[埜]C:\ C֤1Y뷻WLmvfM අqp* -i6g)Md | pN#d3`bip93qtQo$rc~pmPhQeub#Xkm,-k1i7Mg> _)8/KA#xuhVKe -;>[9Cs%*8竧ݴh{2r[zjW4D1ͲUoBU4(DXypF1q2D2==u3*ki[nhH1BP-9|c $Lq),Gk6Q,0U]?fx &??5kkf3+sCV|&.C?^:)2jF~^sݻQ-FJZ Hh J. J^aNMD0WHhRO8;C^ӗ"#G};o|Oik}N/dqܶ] (xA^Kb+eE#N^\s~_eSCI;O'v\ )n_ Lj;ǃdQVqg>`d<2I!p"6D۹~[xWl wnc;k< E.I!4Y4n(aYM3!lN!hYxtagGFKvSGj]br JyO/?̿ .67 -endstream endobj 25 0 obj <>stream -x;]8r>l?$&5H^ٹ$uӽym;[J7*HK2 lYU*Rf>*?l7ٳ]m3zfFj =KL$T%ObIEz?{If' 2?36r̯WMtLʨ2_~wu..)P$% XAE~l,Q?+ *~ )tif9;y|h2y;gI͛ ţmڎIByGlw?KrE~]uci]V T%HyQAOm}\: j\lZ7(%s u#I4P.kRȐ^w0xc"iTJ\+ ~Ջ?>쇘bL5$@Tp"c-jcdx\8]^޶1N,ZeB*XNhhX$qPuY[ }ǎ̲+_QΠ]Ƿ/?~ՏoW?ڇ/_8T|ˏWKhJeЄ6Vnue\|\\o'KZ vY}3f<\/ё^b]B Qgc WAegNP4!1!G50!`X l|7ƬY@ي-VO[AHg:¿R+ELpzed2$q`4Z,¨jta76nka>.a Z; 1k]+_ $O)"BޚX$@v1j)X<{uTA "'!mb f2h|hhʔ);2#A]s =0G6C0789brZBu7ý|rM pCxU%vzd /Nn -oP( l^ -d=M船_~Ͱr<,͋nӻ~ă(?@9mf՝qV1ƫCU rx< :V(GD7mIh,@WC&7 DbFj1Kt/  dOb@ )7c,NK -kpW` -6dGœ<H!<%yB<TEhcyZSI96z=cZWq>לb)K౿bq 0K~~/Ss "ɄێjFP(J-οBb#`@`t+5 TJ;b[N-?-C2O -fr|©qѻaþ:&kD9'%87} -&y' -a5?՗PrM@EZ]2F(Q%he Zxy$ZtFfVkQoMUy/&`*Ej -;9jOI8ZpYPC1hǃ|$2Ȯ&b}M1ʍYg;/ >w~tqTx殝. i Y}´1Ռ0jgA\$Q~[eQN l&Q^^' !wƍ~X;LaDiާo՟$F'v|etM0JԢl=,rRgO^߮t]\ze]6Oxz61!E8 &J&mq6F9*UN戨 =^!DZUڝBiOCj;1lûS `*ep"TxV8AaA UM5"2CIYF~*.C ->ne\2y+x2G$_ 7fd:*XMlom=k"Z!3is,c%ǵ&c4?ژO]4U=:޵Xw'^wntSS]GKԻfCs -7MRXCm(+̸~ y$6^bxquՋw0,\Kog7 -H[I$TaB*r - - (bHS 9L`kHr1b2SFULvdJţUk84X`PQ؏d؏,$蒊SX e\,8NyCU-[l^/0y(AC\lyjxB= -  PFj(?5&0v (V2)HSSB|DHpǎ90LS+d;l0OL]og7Ck@4ڴ`+~&V3}W9~gM6 nf)V>9z;OT „Jnn".U -]v?",hG.gL̻75\Ksl@oh:mu̸=ful4_kSmrn4mVfhsw)˦)E,Ql^^DX^avU]y)[^U]- jL;W.&JA 6 hq`>>Oo90riGgI -NM<0XS)Dd -@&@1_҆PPRݸ93@?ax=9OE4'iCR`BsϹca 0!kRZڣ.&.4mݓ˷ίS6xś^woz]sѰJTfD{{{$to<4+g$ TYkK\M2x0LƋx]  t CUy,ʬ6xҿ`Һ.6Xھ]HpLJ LsAvt׽K/07Ǖc1>6I-&،AHΗufimYttlPÊk~afiV5i K[zrk{LY]@[Rm4]t v8lhg{/Hu,e"c'X,]X2-:om^5*2" 2*5Nk]Ndhq=|۬ڹu +3|A8rʞޅ]V'%$k] -[HޚۏVM";x*1)f6W׶3BAuX9xrrۺc^x1&f{74sI' K3)s0/g#wHj@A;Rz3:>M & -Dޛ uѤ (5&~F o:'u}Vޏlıi8:֖ 9n6Ū0vI2Ѱ+x|YґGa,sywiY9uX9~D5|kgwƉ*6C}OF9u؏o.s3"j[@ਲx/ ~ٮhpf.˖;wj6;2gm|o"pYXq8mG,7F.»__aʾS?X,YY)&-y'~w#>q e7t#?_قOG<Ƹ;8f>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>> endobj 89 0 obj <>stream -HW o̍I>(.!jBh!)mzHG? #y8mn{EчJT|ffژsQXVSs0! os(6QA1Nӊ@fWGR -لCOa숇\i.kpU 2ΏS vYoCmgvw#E_; -Jyx+VY&R 2Jr'Rej_e+@Ȱo'q y}nV{Xu+sujvl՘#E@~AJ.X --kZ4 -endstream endobj 23 0 obj <>stream -x[]sFvrU*>d5ٗyfm?ɲ+J^MJ8I3z>D)<$ڿs@e`so7f}CYT7}m꣯T̮Gߞ}\\8gg Υtnf˙b諧b|F?*b}fg LX/> _?OO}N?3dAe JfgaMMe{yچ/fw/qh5SX4._6es[Mm %g$8JToOreG23̚?S6[͡JYr.rry|tnDg&w:p\qJ1ri(ZF55gJ6?ε̅)f ׹ՒuӋI]|~so{V"g7Sd_ 8anokMܓ[9ţ"2iZLFDPv3Z\ GW'/لz(Ǔ4ddJT޵xZ~ܛi(̬ṱVˎ WٻasPCf'AEi> `nf*8٦*zK*ax.+,LM6^^z_ kdǰdT]].iNux+ޒ:A:Z[ܔjWmC] k5'r'8\+A&~o +RE~]8?fs<..?t}'/ds77-9HX -X." xvvgѯG<;SF+$eG8;R"-t Uh|Zˣ?9PwPӧ0bQ-nᘨf:z]|X!5 }FDSŒ&0a FC?4jefq%L |* #f6!< -f8Oa^=Z"C.`.8p0>gѩAfkdSh5AzŬqhA;3disԉfĉ* -KHg'Fڃ3(nB<(J"'5KA'fME&M?J'D:' -Ѩ -hjHo+x*R-_p@J2&gT/T*_PG+r96}!mD^*A|J-QqJ'j&Atf}T_>htpB%'a0_䝝Hl.HPY}3xA*MwI7o{JV9}զ-),vE j:*yrS~x"/uIjLi _7ɲI9tѭt$r/!\<4M4EuY½<%]HZm+ӥߜU/{4@ ->ӐYT! - TICnbhx6@m,FjHձtQ/1XdXm}nDvlU2<uѻusMV^ Kp ɒA;LJݎISKD,"FP<}]8̟E4xSWN.o펰fYcS:a P$7zwn^/]e0oM}imj:dk-V pֳ<U߄뽂@H@?9A&!Dp_? 3e -Ck,l(#w0,ezidQ ?j0W/e-GS:J,Yy&mq\8'/n cX m,%6QP~3:EL[436J s5.]A~7< -b9 RwRʗu UpӶ(}2lf$H\6)h9kbux܇%ߠU?Jtr5S(]':PBS)ԉ -d+pr5ڗQ5yTN?j4Pfν]S,;DŽ-'qͷisGpY6K> Pnpq <8<8X.?O>=NTs[yiB"rNgM{F rv§ qӔ&!i4GKwk}lZ>IE,y _ EaeÛ>Չq' )alB3PښJMŵ Y_ -/UUE_Q9j䊙\n= }%r!Aév$;+vY.t:{weku+ϗU4"fu37 ^Ln|Z=ƒp75SlQ.{1#6 좋N/?$D'ҷ]Z3tb7!:}˺Z^-K];2=!M닰6Cs7Kܑaۅ1b[#NB|4ֶɺd<|quӀDtƦ{c|OA|]l[~ӷ _yWߝyq}Ǘ/ç' ~xœɋ&'i@Wtw{?!)~uzɋWgWĉ×Wo/z'iOKq!;li:'Й}||q..ˏ3 !҅悎r|3>lyfP9N1#ۥWBDJ]'|nN "yP1^<4&e?6R ߥ?АFW/8^8,YwwUK-NCpu􂁓cKҽVMzԣBݖ?6aӞ_h?}C)3hxp}&!N:*c--n#>x1bY j|mr[ nUu}v˄ %bx^7;"}x=Ð2ł[OSY{9|a_@dEZJ==^O~}-[i-ٗ+Ra/w'+('v)yqKU^C -ӷAmaw0-tηq?fƥ063ttqPMG[X!E;̥P鷡0( E*$EWLJPɋ}'"=\\LCzBMÛ[c*b.A.%Pzo?8ҁ㡹{iΐ)v@C(RNpv_e8bhwd iX;:ynm._xCȖ6Pÿהbb(~(ܔϜ2uzL;É'Ej)s̲|kZ˷+_V]퓖-U웾զ~[{f@h@W(Iw SirP -0:H^/ -endstream endobj 88 0 obj <>stream -HW  ?-[HLtX&c:ds0f3s4_ƙ)p{7  f֮o:!qVqWսOB wt.L;@W&F͛7̓>EݔUste =WgNOT|~* XX  -endstream endobj 20 0 obj <>stream -x\[sǕb\M7 E~[ֶvkg$?@(1KZt\PZ[U&99}wsiv՟׻vNrOz.gbv~y*8!ǿwUs9õtRW_)N1UI]ܞ˹d|jw?K_s}YON=?ӿq38>?̉s\Zen9 [\p:^>_$~$^|'S? -5|S#hD(! m?$Bugh4Xb͝- e$ -!0V;`M+rjXZa+FKf;q3пRʘ{!V=4H*ُeÐ:γ[q,0xqI+1y_iBiM G7(Yq=[,90ȠK4⯞.Xx7xR;!ғBH,`d\* -$3a˿%\ 06F%|xNZ;VVѰC:1e QB>&ۣ-Fm%a1<OJQ,W48rzcFdz3|iƠrogK𹉟 'UKՅf\s_ۢf=W.22&u3qDi]y<uí">S!N҃/B 偸\܆]>&o fYϖpQ6jOYVᰅYZH?o0သ70w5-`p#(1̤cV'g -xg^5U/#⸷\nz?mYЂ qŃ R%giྥp\/>^\_]UT*Y߮wfVڈq,6RmKA'ϟ&ˬCKQR|~ -CT5. pp"XB,Λ qd0# cy6q0Qt5mٍ NRB:2h4Ä~~ՀHaz\J-%-Dxi&@op`pRflcE"|v2 -.^(ą,FZTB'S0W -hXoh30"_:GΦļD&-=/|%=$=EPY_I% 02xqJ4imnrZk6%GM m<) 6 @O8Er\ =tT\bMY``8~%әbo [ X -`#X1%dOx(VF`Ò9꜆/4 `-@b(ΰLkrf=oB" r9IRj_,BJ|Tp2 -l҇,y5%5L -XqBܟi -K9UTa&3HF0C-2^Οikf :P̞a0|ҷ6 {; “O.HD9S6 |=].D>DjfR! j tvyYjiU Lޑ7SOɛEJs _]ZMn1GJv'1̗ÛY o1CIZἌdVjRT67"aist*Q|0[v|='s1MVbO9a"&ȊO`R9D `}̃$:_81ؠ/2..2z>5D3]\P -{ǔ4m=^(IUieEWF1ɢwCs\(j'5ca?kcR8AjuYyIu%%ƀKk`{i"^˽"$mP `# -~b+@>+>I&i(}Z {h0B0u`XVZ%bgjvM+r03DjEׄQm5&(R6ɋE*`rd T0/MBMTxF5-m i}?_6MFכ}lMw7޾]p^gs%o^WW}Vf3ikaK - bݧH4 x0CSH<_ Ue$8@{$$;,\bqĺ%t[*>I\g<B65ےw#u:G?jMkN] -w)s8 - -V ^l21]^{&\2UvOL+FPLI'ɇ7L "P ZW|QGdl,B 햒ɷ}J7'OrTKeCTc;P)^! <7BAKRbg'LD*"bfFJ'9%t>i:- O6J^4HΫ)\!-|Z1+Ȥ ӫؤEm۴zFLtaHc")B3< )ŞdtS/`%AZ3X2xt1(Q\y4Uݼ2qTjH7# 8 <@MQRRcM8Հ ܚ׎0v -Rt-,]+W^|UĜ>AʋJBLM k8#u#EGq2 -5h2t+mէܱsDZ G -5E5;9 !~~ ]*.VMԜ$>v5?_ױ 0B}`١7p(ۀ 7Mrxے"N~9uZ(L@8`%DlsA|y6Q' 葲z,5E ξV^6` 0ㄗ[Ā#?*Z݆\K  -D;/U VO}t+bӃ](\HǓ8H#NNbttrI@]#UjVM=8JNa`ӂME8faE3BF}2Tl^ʂv:Ϩ$S.9k^T2NxmN T~":@Xfٜ0"$Me}WLgώ-h C; i %P - cz1cxBL\{H!{ee*.=n+w4(*BӅ-1QHU!&K BQ1+8URw T%N(H -N/ ^|Έ(+zߕ0f'dt@Q T_32X;>hn'9Iz2YΌ|,0Ł%88yB0FB7)mә!_fO_խW*&R 9Lm.IXL&Fi+W tOF6]_'˞/ GL>_8B(eZQv!u6`;I$6i^ 41Z ,e/~^kNeT#%Ί 5('InB4=:Y/S5˙χZ wZӉ<*೚lC:h_?oϦ胺}5 P]ߥvZfIaQ1U 0mcc -xh>=tjk.BɃɷ/H:BX'Z!& <|2`'[ms# ^NoYc+ٺgGs:pԪD>r :G슎-k&n[W?{\^kLa\ >ЮM.A|ϸwWH0HpK=IWbX)204g0d /1ެPf9tn\n\]Gⶻj1QE -'_fRL.כ]WwU\oWWwv\>nǻyV^ooT뇿U .,52wp!uy(5IqS; -4tՊ܃1-<0qyO)Q!9!qf/5Ufx("ϕ:~ݔܽtS%Y7 v:\uȖ[FNx=T}357/m֓eOڗ㊣HrOm?8+:o7\Z*(ΜI6+j j1`<]- CNŃ#T\#x"/}l:IACudge>'@c8ꋇo9_B4ұ2诏S D+)Msc 6KIszv2/r0[}W8u$fvR%@=,+uL$SXv_q0L6ZP UX(6&ƁH+<-F3,!]/ xS#ڷ8LJPI2sOi/::Bjh* T͚(8ҷtMb/L50xUY8N4|X$s:Dtפ$nJ-5thJŊ%sN- UU)6EEm-l}}axmQBڬ'˞QJ)拇ziilb}!$DCL}q X!,} }+N gp&*H0G*4fsZӖ@M嫤֘s}#_!h9}.}K%}KշՕjd13{šRq);2MDd>O1kOk˅PFj8}Wp`n,Pȸ⍏<>L.,%$DpȀ7s- c1!EabxETz*lA -Z:udU%fG1P)S>/1a(A0¶xq8ԣa qՋoN -Q@H_]#2Rde"70\=ػ6J.Uӹ/#  -endstream endobj 21 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>> endobj 87 0 obj <>stream -HW >,\g' -٨Xn'>q0'_|<UiJOf0^&/4Y=* ke 3Bݨ{6sb.|SZ1&[4sO '1C\3΁㢰_:x1>!^"ם+opQ6@O -lU+hMêa2> endobj 46 0 obj <> endobj 47 0 obj <> endobj 48 0 obj <>stream -Hbd`af`dd qr KOb C C Ci?wLga`ahb`bdH644d```db```d`fddWUk~,M/iuup?%A -endstream endobj 18 0 obj <>stream -x\]rV 7'/|$qߝ<%B1Aj+bwK,TwLp1+Èx35UTUsWYnǏvu?x{굜%J'ߵ -a4.)x'o/_}e%64Uwbtm׏b?]_nW˻fZ?O/oR3BEsf{068Zᬵ<ۿ]JZg - -:HtA2~(vAMfi^&'~\6.cf+5~?t.- ~bˋvzX.y/n+o8о"cRbd+W`w{֕lBh寧^MgsKT9\h/gskeXUP""$9z7-Sd's煎 c9MAͲߟU.>vyDL\5=a%;Ԁ+&=]k 2}o/y~y ]<^Ylq S5Y=7'uHRr|3na0hګ L)j!j[F}C]!ud/b%W`sڻοM~JD]jQ܏o/i;4PM,0k6}1D wr@p3oШ|%5wӇ3> +!.V .LdHre|kp"[ q` O`jUz,P)D-|eԧiɛ*uro̬EvZaxm}Zp$cOF%w3tuO!:E(؟pضQf,rʅAE bdD%:yg[ ) Ey?4@ٞ|ؾun_VP|)ΈXcv4k&2%b0eg 0/=NnlG  LC!,EMLg]|𖱰lbBz0L*\53:~Q{SIc48(qXL@E*GCl^f}na:ev'UA[Cs2yxHGߠ ZnwIE"/\a$WV`8 -bpC -\Q"ci,-GX" "x G4B{y~r8D -Vxb]ن_3Ǖ}2 "Bp: @CcwUaYsUtiMx7E)uoY6}6Ev)GbS#\5grpR vSg'[?ȚƍpM".ܮi\p|x\-v3@z9l|pӫjY[=TyԤg‹guqC3xf_RBrþzzHChg60ʆg8Z߻ÇY&]~حnۂ,v]L}x8. g6p҉6c9P5@k׽TaHS3:F[haE G %sWj>~SĵgP=FY9y%Y1"8P"P!>6~_fV06 }QN,Ɂ.[fCj ښ1}{nY;WUIUA ƥ zyrWuLDʧ)76q٠uvX#5 -1:+tJԣ1_@$@+cSN /ҚsM[dF^fdͺr]t (B{'5%6@rӛDNT#@% xTȢ \kk3.P,UWV;q~cؐ!+K<6{-a\"iϻ 8oOΏ"`Γ1)sP7ٜ+R2 䂳0&b@2`|$3JdCc: J .1xg+I*ĭ۳[vlhTjf8 ;VJǚ1c] -RӃ1. hQp.#d0 PZl+MLJs&s&x]&`q#ӂE1{^U0Fmf\+K^ }AaGEÅ8*(s3k -T]eQ_4oSԺp뱩CW1#TU}*_tɦ9B Fy6/(St[8]aL:bPKRs8{&.\/ F? -zOd'EK@Ff*L0= -y*-La1ΎhWB x0X>41?aGbAôsiޒ;\Ⱦ%NG fn˭g$c޴o=/Y}64%{0rp7oࠝ{nk#^Ao.]:e]7gsM`IVRWzI"ɔHJTJEG( 'moǾ&#:ؾ]$)K?ZQbZÙl!p)#Dv x,iBztڒf "d%bÅ$ˠ)Kӏ0crrW8 - \pvY8+z#u9XөtvwgȽO{o:C -¶K|fg&q! C0Fe%/+CQ?¾bQc`(\1_71EF-\׫?i7o5o9 -3LU(A}9Tleꚯ"y5z5U$Nǵp.x .ᇦ:#ю{p CfA@+q  ڕx$ϝ8P^t.^,3dj E#b0aII_0A2J^(ryxl"AU٤'5]_`tPQF(q#sOQ3}Eq^*K26OCRgA -bٮ7cΠ&(6V -*RH^cAew`#\Y$yc  e 4[1y Kop]U !h)cs!Y%y'U"%2)Y1f"bl@& b+Y Ml+N㉓ @iE4s&45*͐MR[~W[26^rNGgˮxJw0"4>"M\J \c|mR=$\Y4]N:y9qj;jliΩ10 :l1v 7pѾSi8ábm*^(᧝ K抆.Kv q_Txfqew3{rdEm` -EصH@6cB|3 -- eqki9pj - `0v3R_6fseru״1#ˁpIiVj1ʘ>xT5hp=wM5zYc+n8K ;vP1ED9 xRNxO>URo -5rġ'<{*H#xo y畩XQ0G/> -jL>s YHpAmJ9nПIk0&wFrAƸ&Β AULز}7sA|_5Z_(hyUQn=jI}_0~VEP Q1@VuV#}H |Zd yEE3~>A&QiY0?zEZIe_t&ҍ"o^DC1ݪUU%X dgUgbI4ї}mH`?pO ?9vCS =6 e]sJ::Ŗr02h>Y]_p[rF h'i -k /*:hf{=3y6p VٳƉ辞>8 );X!= XK|@&j\o(<%t?'ԹpyH/q,":r TS"ΐa=P_.C1 Vf@y~OR)h۳Q~iXƦ - =>( -EJ؊T=%8=_|&Oh3'Gn O[q"zWFn)jde`"ųhaomA ?SUA\ ~u7Nnl jp|4p&uj!W+^RbujRNqr,2c>H{>\_CF/ɝ{ief._3 v=t9{$K&I}Xφ0 H棁ҩVz;4/ -endstream endobj 40 0 obj <>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>> endobj 86 0 obj <>stream -HW  [U,֊\8v ~p[wos cݏn6.خB uTCr1 - -MR&cq ]uk];iEYv9UKV+\FN~3W^9ȵ!8#!QletD[R5u4FL=z3',>?(9?g]0 "G Lk,j3_F~qK"BITcQ9=uE9n< М1FSkP\G$@<\U~ޗRJH.C3FCqVdbW{ǐiG0mR/v0q{]y>ts(2TRA;#9{Oσj(NX0M|lIN -endstream endobj 15 0 obj <>stream -x\IsǕÌ'f3(xN-YdyHHb AefUeU5naK Z2_|g~[oz_=W۳ϟ}ٳ3JὛ1t)3+}+gn>}5dVi>dIn36{6ΞR3V,Ͽ{?wJ.0K-g\HLx PlΟ}ߓJvJ4-%VэsY2!Qޟs^O9PO8c%yM,oUˈtiğzn)LƂh)‚UH2F:^/ƞ2'aęJ(1_=h2tT^/1x;,tLǤDFnAR*/;.Hk;Tq2.N;P'RiI)ivu9`$K3xvS_J&KC6b-vpD 8f8]/5DAxK Z/H(vɮ-WnD*9nMMc^jG]_ބe(.T}$ޛjOT46{BW -jHƻRsd;҇ a=Lεuxx7^j `lѬ 6]fODߥ4XģK kHI`qԔ9*70Fs 2ƀKŻ1>teFJRHh3dKWM]LDMOğAbx.v,6ݿK-iɾRHx\ɤp"B] ߉]X/ީIZAwp$R ckVd4Ywt4ALfMjm=o32m#eZxlc|U]ۯ_?\.^D^כ-@x]kOo߭1k^l_D&3rD:yK6ͳX7$bOS}^_!uNzӼY^G2~Mn+!IhnSo^|QLSM2b3BrQmSlK-:b೟}rMu[CܶM0d2֫w<lvӬ킘_4靤Tʺ|et?œ M;ňp("{cȹTNs`%߾= .Z.pl0C6CH@B9c3̴(%As}r5?@"%8il'l8/x/qR佐[Hk88GQov@9Jf' V8 BTsDăX쨍_d 3m#*dȭR;ڇDz@X# b˚$1n-~a,{Rg8.1,C0lv{&e{yv0{̧O؊(;(.}};6zb"0J<\]g - -1Hi c .1H$@'[iga Kjo,];`!7(x?QCEE}Ί^ل 8% - ;e汷KQmc%QߝU"aDPxsTI]۵'{NXʱvQ,©:f k68CMH`gT|oCڅpie!Fjʌ'u -aFJPq+` `:ϴy1, x^2GuD @''@ rʊZQ`b H%~?^胇'4`c%u,ޒv'Ŋ'aK TDE *Q_.[o(D~^U6׈WEf 7ֻjU-m۪Os^㽤Dp}զ)t *?=(s{A2D!.N%\|6Px(Z`m|4Fhfh&*9hSrHRC!c~alB(Wo讻G)G -QUE}4xO8;B '"İ" -J 7jkj -hFh.FF71dABM6`bX;^tlLO(u}(G  -c)`,cXk8k{ShKҔ"]1K7`^⥼3#vZ}A -h銞ͻ6Mj$;r[yƌ٣2zr!BS.·Ee]@<;Gc -dJԐ.K=JDԢr%̄Q` A(cA>V{7^|RM>MzbX)9cP2$ܘD-񗗩Z$w?Iۃ8ĀB n|&,ǥEMh)Iªͫ{PeA W@vAq)y:ƕ[:uSvOADH4W+f[~iy2A+ "77n@(E[CR =_6۸)]ip;Xj8)X\գO'i.Yr&H1h -3*QmF}$}եc@ѺJd$@jNTS 3`<qJS B|hLû{C6<%ojא? p^jdOᴸS )4F7I\ۀujDpb *bIs+ILu&0]ʏ|-c[ qu68LxhEW34]A<0 - R̩ItaKI@-ߘHχ.Si'Ӗ -Zyr2ٵR`&>p5y*UjsJ'lfFq\[hs5Ͱ@ɏ@q%s^WE,ڂ>n5Bm)vzJ4ۺLwzCx.x*֮?  -ٳ_XwP嬟>}#t|烙Ir ON^ץb?42 {~XI_3I w=P -J߰ ---[I*JLkzp?J] -Y:~E>k*,ђЯv&JШl͛&^ӎԒ}G܅NqYyjk+՘nL:Js: !!cC '27u^.rUJr?ʲn1Dˎ]YwG|#t`BJE s-|{fub%Ҷ 4볮H7U]Q]yy#+ApD&x`~%BM36YwqщPF7qB7A.ѥ,@#Hж23FƢzBԶ'$zr= ٫p:\)nW_PmYru5ap>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>> endobj 85 0 obj <>stream -HWm >,\g7KElwz(| /~$%T;Y-Z۫hM. Ն~a m#dbEPP*ɻS;k ->V_#B8Q#Ȩ}cS0cUjP#Bƌ -Yҡ5|Ug#veh+"\+Sx#8sf}Am} -q|Qaㄾ Qj+xmBG'ׁG>/eA{0 ~V_YB^+i|'fh&g Ax7|xVySw] q^NusuО rtNQqf]W,-8r2i:B7ƳPp&ĸ2|tk<=ӀX -endstream endobj 13 0 obj <>stream -x\[sGv7**G}M(8r&aY E*w{f AډT% =ݧOwN짓/luwSf%fow'_|BJxf g++#c =U:åpx䋯wfܝUi> u\ْ=y=NyΛfymn+B0;n׻͞]7Y%W5of۬^шr]u`"TJJ*X7kzѲ֋y k'2oj1HXn@f=mn -ϯE6as(^fz=[JF:]^]ҴPV]}^/LW(ռ٫lfA 5YpxUj|s軮Z]u.3tM|={GM?beLXb`E!&< g'ltijj^c 1J߯7k"fwvKehb_pmͶ^6bͬ׾ksjQB2>tcHԮjWWRgP)V>j}">]=?.ެݶu $>go֩om^N\m3]]ܬ.z&衐AdݢaUlFVl6S^q,|U{C|~n}Yߝ.7 誅Yf=LD{ۀ3uL/5grRq@(_o?^w^}ܵS{]#HfʄQlS0Za`)K6l S"_su^櫂+$ EqXfimnJNM <|Y _yFvA** vtajmoaM!ɀUoJ-i-)JQ\}UwFHiJXDZ PelL0xv(]9iF^{zL,|$TKxr-y=mw&p=^ެaOpq`ŭ֒fIS._BSx.ߟ.<a^4U.(˸x誴2Mh<W$`B+a)-ouMQNX<+Vq%&#C I=~rր>?oV~3tL_nmUq~NPs4$k83 {zD,#)g>}N\;(vRΞ]~̦z|musm_k|챋ݛS;檹UO Yoo>L%[&=V>f?4wRp@]=eVq vܞJܟAͺY'J_/UWqS1=FnDVQ,xۓ_nB!8pw ;L)M?;bUHz:mv1{5H5&qnWBa_ :U Yrhkab+^ԼU͖~`8 + }~2`MvX}<ӳe6H40|Q)5ppHRT$h(l"L>fՔPg<,9{$ݳyզ޵&!)v"zͶ$:͛i:NVɇUj?Y˔Z\*EOI9 J}12B+%ʍ̮N2G0GsUw :ր73O}r퍜8`S)#[QR8BPW]q\7?9C U`!B( [Bg\Rɛ Rx-.WhFa R҈?3 LWTڐip<( TVd)0TnOGW)7(6i(:8IAE(0e8%vXAěSΙ-uR`!>̥n8N 0pKdOBf%̓ݳ(@qJ`A3;da_ -)V -_i((y*<~U5:C(!N$X( -*W7RtAhys*K'JerB|y(tra昕ZI- q ysW_]g[pf;gOBe@'TO`[>Fn ~ɖrKY:< =jY焺ZӃ~Xo -Xp݂ 1Tn=o3Mŏn|#4hh8zJU 4CѺͲ[}Lb{.52 HsQG#hS>lh3'm!޶(Fj^n7WhyoNiנ1sPXQ tyHaM>qDV4F3N*l(*f -(XLrps} X_瓶@qƇzu<RzSY0^'GMh$(ft^AΏWP' TL}{ @7pSmd:D1M'N - ) ƚ%H̨!>B'%M:@, ÿ}DG(X(|v -́5U9GP2ܟTBc!@[,z?,Bc.'!IWL^GL-K)2(&NƗx\ %4hώڑ Ep)z~^d&GaX>9Re(@9z=}l -l`hOKhp,dbR V:АU X"T">bn'@4׏ pK0hd%uޅX - Yh`!k $/5.K'P NKbtd0.ox dMWXnMBNڂY|Ŏ+.$iQS:y0|R'7&G'C杒vHˉw$[x<6ؤC˹pɦqnziܬb:6nJ %Fal6T/fY~F+S3(mq$eK.]8,zM[uHFJwA-盛.v.Ӧ^}˽yj #7:r&^ -,wt8)#jTK&jh$oOm%(pz a7M'.VVw67KjΛx/37$!5%g\aMnŁ2)ߣ1Zq6]d4}R\du\4]ݵ9\w,fdnִKm8#*'Eesʸjr2 F=]C hs1侯{.ޭw#G.p"'M2'9n_+F3W{Rțf泇mn翎AFuLzj?_:,(qGot$Gӏ[q阀}4WT&Р(*t#YOҼb@۶*\/70U|>b'tj W#v9C8᧘`yD"^9 F7Gd:5x,#KxD)4+3 -K\;S4vڇ+G^: l'ϟ:Ȑ=\afL  kHX`K],@PNg@f5F+۲>VUbdSWR:>=5&8H!vR,ާȑ?ƦPM-5Sfp S2}_ qZ rJP ZO5\s}Tj,c9՜pnQڼ9EnфR7“L -(V""V3CQ|UH ;Bir[AyX4(,OWxh -endstream endobj 84 0 obj <>stream -HW  ?-V9&TM6x|?N-%Bp j/D-9_*1\>'a>fhOJk#>+$LM_.eU$;Jf}=f߃!Qoœ!LIckRz9C!t'O?l4\%L;#8uKEk\T݉F{ԋ엞EUNIZzJ#`欘2+$ rma#sC*An=g[>MwTL!ͥfJsbInU36Lں@Fų0+ Kt[NR{<^?gH -endstream endobj 5 0 obj <>stream -x[˒ȱ+$#D{5F}ayAA 1lC G>YGϕ ̓'_G/nQnjFof؏X|LnџFl|~+||qVݘ0p̏F|_.uUWtfb@8-K_pk dzߗofꗒQ`ɶ=<4 k\v?uŰB{;[`UYBUki\>J{h),k[Ogz,: -gZQhgdOWW_E3XCE]!TRWT WNq!,v΄ -H̐7f -%|} xFUJB8&? -ee;BA~RG90SkنrPUfY <)[D-%tAuq^fd}TٻShP0,fV#`Ne*,P:̱`:$`)\ '4 fcQpQg T#uЙLIU@N6l8}֝$beBn7ㅔ6;q4iM&ޣO58Yr1ʅOS\4SsTrcHYUʻç p ރwR'?:h6 -;d+QHaLPRJ -B3.%o]?6MI[|֜Lpw H50H2ٍu"XEE>?ٔSB0^.}YGHB/v}' zy}\$GF&QwFWH瀋1~@Wl`A˃-@.di 4QN (}1z*#7<7Gzjup~P})τe= -VLM)5[YSxTV'7a/P#ToLS0Rېt!}J5KA[_#! u6 ˜PO˺AV Dz@ HUVpPtnrVpChz۸}+0R|ٗuST|IW.-4{{k dd^wwsEIRɅMZTsєڊċo`R lh/RӦ.!7 p?AQKE(1|NJk&PhDǣ] -g9*GFF4UTzsqr>|vA3?s4>, R xz|Y?o3*z˧$Ie)̃A,[[͟Kdk -sf+ZsW~ dyzyH-rIP55FǴ e?3WK /)X[Kч b#9~xE1LnOR&s'"@dr(S#+4 j%_F|`%tc% ^vGsG(V6BS1Vt.}^Ch9p.,#]Gu1OaH~3`Kq!l0d+:9RȬQgD }?2!u5_')pCVnR~pr < -_]Nǀ^ZfruIڼ%=^#~Ǩ\Q{K_Jݗx"MwLLWv@CSbkz ^eL{*~Y-<޷7*p#jXUsPnR &NNCW! .ԬIQ@J:MCEw~|xoӇݻ77SΫ&N7w`y~NA?сr)p?!I:a[dD nNvŊ ݏruuM.*[y!5ѰnnR/eM6:Sβ۪h2Ĥ8ڜ S[ZpfR 2 \nMa DUN׽JjIX2YHL7&XeA9inS&}Ph2gj?lx/rYmCy3.Qۦz^Q54͠fE NjRgVm~'Jxw.Au GQ22a:+}41Xф}SGQʼ=.89=unfC!ucvڱP f]Q԰YQS -C׾F{_W)H.w:t"F)V6=ilܪ e<&;ojhR4P#5%D#~&Zh.Ԝ }}e7<+9x1_r뢕'D:/~ ^|-5%v %ela.:abu`XfmF+Ȫn?j5\Bh?Sm)Iٜ_UZ^)qiBb5EN?g.&$I՗y(“b% :J;ڡs愉SH2WpJ%2ҡ&AY:XN&FsOMU&)Σz4yZOބ%y|鎡}(*Brp/̤ӈ(ET2T -m+cwVP+w(j4#gĨSH=4lx@7SV^]Ӕ|B6_7D]ۏ=1rPiFnvo3 m%WQFGg3Y7qνU\ո~ -K:rV[{P'jzwǺl8,]K-G&_>s2zY mFSjWT=sc1eeo<̶~L>w:D${x'DtbNV[c2ҵGSq3{ ->HjpZk}:qR=sH3'S-(<% ݾfU.m=>ЕaW/t#SE[5lr׀)fRJ _"ǞWWx#CMw[ic!/)k矫UUW;:v-g}hU# -< -D B|n~Hc)$;]Hmcm"wqѯ#C'i?[V̊~Hr Ĉệc֘Jh%8 -rh_/~ys{7?QT't82 q9M* <+GjfliKj7nӕgd" )@UZ1jY8<| a5(7+~ä}G f⑖IEֹ|0wVajCV{I(J~:~sOVşU`#/ -endstream endobj 83 0 obj <>stream -HW e>}X'F)(&ڤ)\shh%qThH( B#9yŽ̩eIpFNP?Zgvrd\a %ȱN,@K<6)LE$sjNxdjȂS8rbMɧ6'U=ipANeNоr#E=JH{='Ͱ>sNFZ+6$RCTV8J&]Ncr9i;aKߋ8#uӢK4o{SW@܈qr Aܽ?μ=gS.m ֠T 3:.aDP>stream -xT]MSTT ;󷻻[Q cݝv؁9z3syo!@ @ @ @ @ @ @ @ @ @ @ @ 6O_D0DFWra  -gβ}#x;`J> Qx<O b$G@ Ġx@( QRO Q(@ !, QwCf~VasP<@ @T`ّ_bѸrQb} z@TPD ~ޟ+BdOo_r_XJegqys9/3koěKtN|9<ܢ},G/ko45aaxj7}4)9k(Kg}zܣpXxFeJ ?\6mђ]_>sۋ8O`i^z&9]_"ܷ̟6cnwI<ݻxƴy`Q\~!Bnb|kPogWk@˳fj{aXcήp\nmj7|8&apD/>:0WUtgٝzc azO㪬ܵG ,@Ę(cQϢi?4W8u4T139q_6ZM͉3D?R8aGQSӰu{{^ݺ}#%s]!g>葁2S[SvA0: -i);P\`L®5puU@X\%SYK C^`@!yeM0O, rԗa؃\ە1;@A` G"ڭ|^`ܜMXUpڗP&[R F'`aTt1?mv`1"LϙÝޏV7qFvj3P"N]//qf@-/,n:0]N{gN~?G[ГW4i1ؗ~\≔U5/8&bm@:؏Dw\'zܒv]zF̤Bl}Rf/0 WR?}ɂ5@v46> soܺub,7ڭv[lMX(mF9حlg|,z&ktþGo,h4lp?7|M,i {ܗc)ǭ>:zG8cxk.mOC< )ڬɯ'8KO&7,mmm`Hp5k2ęl$2+ԠҰЎt0#g>{k f0+ѫ+kw*NR&||N|Fa+y&_ὡםټAjnx';W( RO<=!g1c܋)R*eJ~; -ϧ`N3pܗa3$JRܬ\1}9z}tƅJJzbf܏EVǏ}'cD *wqogjj1,|6r緱huss>#y~k'#qO zXE5ެѺojE2Ϗ|igk̡C.*%.=sMA5}!3<!&Ln룒VOL=%p Fj֝1Q"[# DAp -y%NǢÚT ۂT'O<ߞXÞخKI?gư3 -DY'#ceKBySp{"5c&0#=|.>e &&L%Kg.D=1|&όwᮎ`5Q"akU;pO-C[4yZ`8? {%'g<Ȍh{~`2OO__%ڮt4-怶HjVFO~0ÒYKG2O@!hrUMJߴVm]?`BL"¾g{'f\i!&%s ݣD * _S|kBCܡiw UbuOzՓa>7++,;~&w%w؏Vi~0227uC-ٞ;O'naIԸ0̭ aS^ Ԙه2zBzhgf{X#/-0O|ybZv@: X}j٩e\+HL<lFc!zyFf'1̳+d^BDzW1KhE(J.|s .qO^kldN %|.omfs^ɂطF̎~TyjbwƼA:8@ b=h|a,oijE'o/Gf@;5~gLHl7űت~?_~ZQ^ NbG(:J Юq}c3z[US`)V7qC2iװ -oQ fv}.(L Qr~Z7& X=%n4D?pO~6n}']k`Xn`nXDsPؗ3Y?5=֏r2oۀ%-UY D *5ᲁm?e -N{Piu${MAYҘLF]!B,lH+w`#wik -}om5x.g0,xENS:2I3j.^8tOSPvQ+;ԛwש> i8tW-`n+S-j8ܼ֭6mz#x(-(Ch׺:N i 98C|޷-gܶ_t|\vV>!O0JX3@ @ @ 9 DEFY!"$/9ڱ׉} "f+0"O b^h Q2h;Qh;Q4'Di O @!F !0y~#O UT9qq $Kch^~oyT< -)upM:x4u JOui{2KT2Mn5kFMDFjꥢa:Q@{h#KwW}XH>9ƾ@ziK?8ĩ#i=%ẏU`Q3ٲʐfƉf< N[x9m}'Tq#e=U ̛_s.>MSIfMN.m[0{mK~+\2/,^d/x%YHJvd})s$.L+A]n2I|GLE%ŒQfb*9$M@98ke5u^iDe"^yn_ -ens܂FPSVir g!w#O)qȜѰeO.q$jhkhD(Z˘bH=IdҰYiQLM8il&)8\E>jxMyă8˪g Dǔr3ɆdϣLȞeMQQ{-}`vLWO;Kޣ[5+l:&ZB55Ժ5n[?QSuH=0"hL`*kh@G˵ʫZ(m>U(1jPf/szQ\rrx<56ɴ2&e20Tfe K#Gu~3Rb"0 fX5|$W_vܞKؑ.3'ߒ |7Z7P`,0v= |^m{bf9y"l2~V{$^9G @qmBقrqe7Ն!jgSVZ?-Wlr&:Z۳5] ~ -@cO6oA%'GnOu7eSQrWy;g-GOxbj}ثl5%A [9{\艸ۜ;]@u+)̷hzl ުG/L,#Saݿ]?{K}1T(L*|!=0+n|dBsr2+';pyU ӎPd6H$"$.ഹ VxYi' -eO5Ay)6=Y߳ʲYG9`*&;Xm%ih"ĭ)qP;)cKM:GQMIxB{w{Y`GY8bdTyTZKn - M}DB?kNI Kp{\#g<5Od֍VE1K%-Jkҵ(#lz̝x}/bY^U{ Ɵ(N!$7ӗ Zc"zNrmaFnB;[d6ˌI&3)w q@'bB1UpGQxZ7f17bj D,/6~6ޟ .ni]M%{7/Swʐ0WOz&ߠ= -UPB=LZmPlw[U13;V)lQr§# OlTqE:~?CT Ow-L'2$N 6wwc:ל zHUJk^Hiյ ͮV/F -ՠ7O->Ba{Lt$mKe<KelGd^w8uM Zߠ5X(o .j5HBG=/G ųʠfz>KWg\j`=f-| >PV^ӵA;-nH[mؠJ{y4MDΩBS_UPT|꓊ vI.j>ҭ~@czɠjBB* m$%U$Cw -1*@Զ^ n&8ac[P0>tV}2JtћPEЙCqQ#e&g -[hNt(/)Q9nS:oՈ` ,T7˽~-֪5ԼFTGkh -`YocB4rQ=+`(>&mwROLꉀ~*#)28Y.sV\09N=`I-YVxb٩<j9ښ'R}'`m:O8xB'13 -D6؝%#Ol(C<1m=]r&x9;DqOzCZU':`wy :1Xjw@SLAYKG &;xtC{v3şD/_DnF^E(OOUGŤF_>|Oޣ y ->[t>):5DӵP] -ؔBޜJˍ8eO{vwn3}`'^vS5\7IWyB~ѵO N;Tm5 ^1fr~@ڞT'6~]y4IzfaYOkŶ'I.[Jn ja,'mO,8g)uq3'-DqO\ot y<[rE_ $T4XJ4/5'ځeO-`A~@9Ң -N\ZïovViޫqoy.% e.I3:s=Ծ*4=Q`_/I{"T+( )'E`/h{BO$ߨm֑+mO`{4VgEjݮĆ"$ɕ@-'Hs ? TѝH'"6k{1p-gf79>(Bm<ȭ1Le$1YL o|?k-e 0i{p -,U7f+)Iwg`lfac_ӱ^lݮc} >j)3,Xr͆YF ?V^j# -20{F IZ./: 33vXkQ^v1Ӳ zGza}}hzBM(R>=O(7ֻWO24omIJjMxe'橲 (&741Q~FƊN]m;oߵc; v9kV#lG!O$Isrfy=@wE^g&Iu05&:?"SN7hw }uFh%ϵuT4=A .y=uڽ;޳O<1Z4=٫{m[5oҨA=ǚvvf&Fz:Zf&z&閐ǭ'ȓpq4̅Zg:IGՇ`#BO$-{ wבzBo>(鶮֜zj?O;:󡇼Tt'7C̽qqƁro' o f]*uCch|l|}<{K}4=aAr&5NWcwf5n4UA[_&؆Z@: RD 6栠t_nH?+tȻyxQ#=[b8۬{4CL~hQpt;KyjeǤ톼x~XnU "aݒq?#OPA=8Anߦ ӛR -<5QAlSy3vY$דQG)7a -v3 'K|k{(-a\yi3{>u{kމbIjmMK{ΦjLPiws$M4:)iEZ}e=EsWhOɧfP^c -/OkPw 2h N~?{Ɵ5{";p׶aGyt4tZ/ԟąm?@Ԕ#s {˩]R<5qĆ_cAG;(eߘoPO{4?甿2vf}0cɂC'ćl'ʅF -=,,j3M?y?;mO:bs@7nWU=?mxXo;=ط`mD̔5ܙԡ"b=h۞3vVAO -xc#QO˟'7^gpX< B )'*_W@ Ez9% -A D%y ePU`+(s OyiNy<%aP6HP,䩮?ڈzt䩮x;`*D't: A?.zb ]OA CD|ѼUW-[~ XOZeP,D Ouy,D Ou'՘ 'ȃh޻jύl24AK}G'S=ѭrd(N{,8MX_'ȃMmO"yS~'0|2JOy"> MlV-t֑_[^Dc C}C7y -7,wO_4k9ED Oƻ5|O%y>ZƺݷnJ[{+?wlGw5P򤄆r0;E%I㑻ߦn[>8Fc\v3GˋzBX8XνnX ߶,`,6{Dۖ'!G8< 0yxSL~}yBx9c9Q"eєxB)ͼVyBKĿ}KNO\,{=z"j{gbXHx'{W^ZfOHvpfe+[1B#Qۖ'D!M9kzS^q6ID.JA'40dq k;‹_"<[٫ b {4q,˻`?`!';)s ~ پdSgvu?"\J;]ʀv?|9ޭ'-^nٗ7udYN>@c - a6A <&@n?ځŀg[px7Y'1{j~м>%ODok -k˺~JnhoLiqVJ\  -J!{1yKބ]p?PiXEl({"k7P;&˯9>Sڗ\p8'yR"Gx">")!( QF39%FE>٠p,t f {> -vaFlb, A'/fR.艴6 X>➸m dYEOd&-30>+ZqO$] AxcӰ'#-qIr -Wxj,&lxp^~<;GDQUeoh-6{ZE͜;tj>q?`)g狤'e) É%w+/Odc|ŏS$<lA`z6uL$exms~_-(,z\VrMXvʂ y^.쉜UZWg᠒B7/k;{nF@K7P!ۻv:mҙ} jƊ{B>_ JyRU.9զN![Oٲt뷮װ  mkn*("eiXK#T̢RO$^֗ɨǑ$o#Y90 ¤?h.\zMd@XGQ4o_c@i $c庋eY'n>|ejN&J/$HT ˾m0*3ϰP5̒=!DBy4r~a'gx|;gǀo&(2}ܝEmj;+>(Oy̺R\>2ODzv7ftWc}7l lFV'?8,󄵇dwCՔ]=ezفuAW6c߅C~ď*ݪ'~@N2O۫.hoM%3GUU&,@χnOKEt' f;':8:(XeQ#gut^"pzBoʛwda[[#D!:)ʞ>ov͠y"d`/ڂbe+V,_rfX|U+)CL:~_j_&ɯc<&8r'bYwsW4,UܸEwg=TKn[v AgCU''dc~ Aww,77WxBzl|u,?<$? )< ዢN3} |-9فgD?6mOYXm V=*_)6IXoKKr;ת'BKDA(z`{'B>ah&'J=a896'%YFNfPՑ%~v>frjzFB*l3h~Vګ,_x7qGSswFpaZ-ed홶Ýdzm8L֛/uU&DUvO-/WI<9 Zw 1z´L ֬Zq X<#W.\zۃWn:pw˸\f\+'k\ѣM:c&_ia_vۃۻw3Ǭ=#HQ UXg{6z.Z;)WyCG=oRmtwÅG4#;n[XIp0~y2h8+;mp'DGƳJ(~j%% ՎG,eODnИ 1K' 9K4_D9u@Z#oQ([QE]@OPs[au6tЖS'_Ik%\KAYMCDұwvnԴE]f!]׹>'DS .?{H=!9,`(ZZשemu?jxE*jy"e=I<4/ t F$ r\^' from)=yDiue%-> 'G@+̪D<js=wʄ>jsz)KENwN!%'oa`Uh=KAI-](^'lC~xkfggeƐ^,v~r\exQF BP([HO)pd$'UTay"}3ާ `lӺU!Z*:8橴5yO񌥖_' j\:ۤ.\Ğ fwp'F> (g(\w= _>GmTS|'j >8CY81b0}5}|?t| 3i4ЮK䉠lrGK'>pؑj|lآiuk״UH9|O`,{\~h,=G -FFF:l`i*ۨnNuk;֬ip%+?6oՑO?-׭o"5lkԴђ=C"`vra<\;r%_ -KJy|`:H Imb°p@yŨ'ǁ'iz/N] -cFqczVCDC =! y6^C_:RO /A^XoAKP@7߫ -7oKъW6&DN'Y{sm&L=ssS3S3K=egna&l+gڳ}ufar.,s]!âh^qhܸ_ Kh ݃ o,l]4**&ݍ}mﵻPݝnA[ ,TQnXewY@efvY>ク,sfvst6{p]t~pp~ؖ?zFafFzZZfNփ c53c<E -CP.?i7~T,N -Lֽťfd\_8-v.OͫzЂ]_i\N穮&ıG04\Fyz^o21)u0n{}n9!<=&&&.8~bcbbBB,,A`mYpnF?=|Vle3Y$~bQ%FZ~,cS?5dT-yD(0ӠDv`xB2 4>Y=q8_׵x' )7>Ђ3֋/ق -ÂlG02qcdҞα-q8(:ZKcNe=_1UV8kVI_%;^y--[%Exj%Sn̨w9̽7~į& -v`tÊfHސw;S3H{w5bY$? ]?cm5ZԤ{w j$d`뛘E5Iۅo@#"&wt:C* 8_ -2ˏq5ZژsX56(#^}f)Oh~f\Pvq#jOa<|\0^|%x֞`ivE/$ݕܠ{='շ6qY7^˙iYC (q4l\a˳jijRzB,حMN5=9xA+'ѣF#K [$/2e>i 9%OV&?h-hux+^뫁>ɥ/P'2ڱ[)z=!Xܵ'c!77 -}A`fcq''jeA(L5hH-[3g=^+ؤ -(ݺ߿#--;!o;t:u8/̷}L-K~{QL:_"g(?~S=`y%y]\pK\~`Ac$c0e~Xb Fe`i;h2q9} -rsSzOv&~psz㛬RzO=mH"O")R'FĬ]kڤze:D@}eWxh1Xtec{øR,kg34\OR/JyBu?:3%B8ƪd'z֝UtBHE >Yk@ v`lB|VkzN9.U8I"OQnL{ M+WXdih4[Ze}p@#ĵZc%L't皎#H_4jGc]"̇ l\e {@ $G$CZkh72$5'D?& z_f'^(I!8EO7/:D)=9O2SS8{"(rEk8x;=({ -UuOon)]8gww=I}|>_we -QuN=-%1F@ ~aQCy#޾yI 0ĤWd^̋|.<јʏV -xB79] :y A.o QD,đ^򲞐ԯeúi;@(]!zOڀɲ2I}sϨIȻN+$"|}` ճO?Hwx_Lް{G_''ҟnXjK~V._3y9)i<&옼1y%(?#-Cx|⧧eڇC3Q%Y2K':G(M}p{BϬ'+]0ڝW:?Wwb"?~N40^UErOiYyIxA?Hm[zjݧGܢ`[ 2?xŗ혮ZH m޶Cjޮ`u7qEt4>#d'ktcЩ?p.Tm%[Rl`qxB͉^dAvV'JsXC2J<y:0~G&Ϛ2~jͺezo$6m߹y1hݸc#%+1De3̠E%zI%`ʵ~lGܸfzn;Cב'9 *օ|vҍa'j#Dmt5͚]zuaZ>q=A6]zwUgz֥sc9Jn`o'ק%%J6?'q1Իs28!1]WNG/MӞ+K:04wҷTDbNo(=qI+&uX\7#`=1-JOdԅ/zhBOp}!/wy"ᜉ3ngMoįԞ Hns͙cS'_(9xjE$>sZCEB!}hߔzԜWԉ6}C{__AOv"7#R-MJAԿpE{G -@\7|Hȗ:0"=wpۜ U+g.]CUoSХI!_.,B+vazZ`*+4>^ k={3$Wb".9kVI22nyp4~]N߹gm}jDSg)1Qh)eGEm1B]^ 4X}LȸyLx"sފB9ra)=@s8Ï&R."{0Iݢ` e"B&kCG>Wzjm=ܩcDM6bA#RD3›oh\zϓy=tC6TfVWpYihv P4Y u̙ivYEatNR -{"Q!ݿ.ˎv6p=Pv* QƱ[z߯ -xyi^==̛5ﻮ h,ybGpSYei@5I>sgL}E͠m0qL.@)/qޑH+x} S_TDNSWG}T&E~?="ǧog3m.%8><Pm ž{,9$"DZ`#@k -7UaO~AeՆVXBgI9O_u7y; gMhr\?zF/l{5X!Cg}4d VUc -PwV-z:VjlxiY:y?WԍjQ".LN)weez_f2ދ9/GBGVfKuO5̽]BpM'2/5JW*쉜^`Y=} (y"tYwIOFlhHK= - ep8|]ڭQMQkm^Hoa!LN:NASNG޾\N%aDu/|nNSヲ!c5hk@cb^Ly'@kX<5؃D3w-RFm#ʚ]60,n>sڷK ٧mm MuRN[-hSl9Ju1 -پ"}8F{BJxQ^926'BLO4o}sFQ~ Jy"yG^K )-vQ퍉Rj- /!J=`ۭRUiƶ50ܸ/Ύ2 Aw3{{ ,aԷx3{|?zP}os΋>qFF?BF -VFd -"FyJ"PV.ƿ ]$Vo]QBQA>/' )4'0d]5$6帽,R7*=!~@h]:64'/CZGtYՠ*)m<~ {W艂#LF@:Q(&z=tFȽC*5췾#^ROT@R?m)ȴ(ΑjPm h/ gD36iy2NPv;w=ޱ]sT~OsҎdsc/μ^+ߣ'/T?bw/uiaŚ;4_Y'R4[nn dkLy{il!h sKs>1V}n0S[06YKÝ$Z#_d Lˮ,{k6K[򏭎Npg {> -Sx 1ɛ3r= #9W_=!:,M6G]/L( ,F&hccN67qlr@4)$&m6jU ԩą q(4\IrT#М} `os^GIxqR狑X = qu;NҞG=OYE;ꪩo_FS 욛&h Ë)x0ޮ>jSp᫺'~Ey?']fnQGS-nX@R31Aalms6nRD?ڠ7f%" n M/a<*RӫcF#PwQh) CM:^mx|lÒc~ՉطDhB8DϊVW"0SÍ;nmvz2eM=.we_n-{Vdqy'~. bmvXlX -] XIj.[" ޠ=;֌>GElPىk(.6S^{b{i_rĦES<ֹ[HqoN1 5g2'[4?)[rq"1FQl"ok0G]mڧx^TY(d -sDS?1m4jh5#¯)a2wB DgiڤXll/PFTR _9.!} ޠG!v+mnd{nE#/i ' ޖN;k-J"g;-mٷKk)q]Z<&m U{Br랡 '_=)J?|8:!wt!4@eaǎ\V7C/רr[E)B^]N>7#6N8 P3@Ȱ:d xNق&4Yoұxt{!?%=w]Q]Ϧ"'0l3 gP?Ү+yZoewZ5'@5Vt*0r<: @4ٹY`ll22v70[C |擏Qi)~[=)FE#=\msyG<lUtOy'`LV8ěޡT7qbv1 \-l@ƽD7<HUo J>.WǟRhl*;hxYԧSd G8Y4:\ ~JK ́7('n^kgʨ\ -`Ѕ.TwۍtWzROER'VVOc2P\:}..Ѡhzjy:-Yj?ߣF=Jvk;f/^q.g~뿦D2+ -I=Ws90+.o-Dݯ+a - 1ĢKtJvf\E;VG^*>ޤ Z^_xOT=Z$y/ ;] Wxr<q֊TuO伏N|@xƖeqMyB<{yC2N¿桏Tɔֳ1XYgHє4ߩx@[WX&Ď&!䳟|Gw}NF'x>>{')?:vrh(ٻ_/o$'莜ܙ59xK?ok;BWܾ{S&)D2 SMMSF\aB;Pet'8moS3vS|On b),Ⱥ?q3oriAtO^>Ք؞I,3++K[YC}NiF҄:=`]8ApmH6tXYdAr؃os}ݴ=ZZڊkCy!۟;h [{E(X[s=}؍Z%r=OKGX$C3Q$pL{\W6ޥ5+?2L)m.B5l66̠p!ZC)dAKuC"@qP~0\9pm0DTO6]ӦN_~.\^ -\,َZs\n2j;֕ӾKp /A>E{+\wƿ}ܹ1wΪ?H -.'0ޞnim!e)'WiTmMtI^usKTԖ`'AVCN0 pr˳a7eÞ&MX|З+(y.|lm}9X`>'pfx{||صr&8=Qiy+…~7i\FիWDd>u`A@ ;{bw fsˆM>߾*<ĀoS(e!v M5s*l 0Nwv׫mFwEBS O0 'îH>vrOj<,aqOIf:W)UM'yQ/׳5?3 O0 tӱ1]?b&4أ<'ya>N+^bt"l@ O0L>qa+˺ui'yiڀSj;U=>* - O0p&+l*̂<,x7CpĚ, ffİLU< -Ox;g@xA%N'yyn5>˛'T ,}% }"Uw5B` U D6w%٪,yQDqŞ g/:/O <ĹJ= /:W!F<(#Od;J,(̥.{Z(W0,HwCs.hJ< %eBKY-\ [=Ԋe<:JEa{%<$]|\Dl 2H ◨A-y{%<(O-,%4ەO:¯>㭀5c%O WۂkJ/#yaq<)ğ$LA\Xht_ל+sƜ\|Uz? fxGM`77QEꩍ8Öԃ_OyYy F}y w:iwn -J`ğ {͹(,7Aɝp}.7)=/3S}AMK(o\nY* F *XǓ!N{fg0Z(?;':фmJد@gM˯3H_0R%fS_KzW)]ݑ -f>ն fmz{C:Fo/Q&wzˁL!~3" -)NNL˭\&p .d Qꥤ66wOxTXdg0)1[dQRQWd5K}) -^d^hP:&d)Kh -])S!8dohr'j0;S.F^rδJ>Mvob\8M*L~cۃv璜Yn+`t],T ߸'{kd8o)?#_9%\3L'" %-7/L8;ƭD1*ko ˵_Nm [ac~b6(闄}ك3Y(Ωjk-a%[ - ;-Ypz|V?xѽ̙fqY611,ϗ;{Z` P{sC{'tGWrEZ^aH Z" ,5 *YHV Xףf(Wr=՜;uvmoio!K 5p (_WVDD~.ouĶagr -@|tT pn抔r. Y XC3{5 Z8R-~hpM}f`Ӱzn[XՓyU*JҞFmZ#8*|1=Bm?b݊ޤJ&jcMwZw m-\(if|.#O#zK-0;S^^ݩSn(*Hu/=ss .+rr 0uKsKfU'P[ ߘU~) `6#oO$sR-F0nn]{f9kb˅QSXmhD}QZCO(PN0(uǛij;M}"5}O1;E7&Gǀ `*-V[ ޫ_,u[\©^y u֣.gak֯0dF5NK'BFv]_k}M|cxoa^)ojs0MNꪃki/O>d|l^5c9ӻkی7\h4CW" 3 ի/* -'nmzb1s ڨ70;9pV7@ _.5I1/5dt#:˃6r>DROv -DE _*HIgr2O&fE#$ Q>[77Oe#K,Q&̱hYEB{p-&U=Xg؃P0` 8Xw|8O?XjWjw:D?BƌpE1[*5NfWB,dkAJP Z{$hTa5+(Ւ~ ȅ1lubsn6QMڲ]} o b׾-jZ>șj YH ?%pj:8M -nRfJў78 e.`AH,jGJ>6W* Tv/m\YjjQUZpͯoNU1-}}CjOdnl 05+jQfԻJU̥~38#%3ߨ۹ʓsLԬݼX4 QYF@_5wmլL8iܫQ5q9I+ϚTdx@s&Mqqjں㨐J/8K^ߪohG%%q="`K~%fvQz5?R]@vVa\+rUu -ŗhՔ?.NOr5д}FIbEYiiXLW[\&TZHz7a#zUTY/جV7/(um5##sTxx˘ԔĸWw^}jrݳ zQ(R35%%9![av5X,{Si!{,-|0o`8mF]ӧO|c -D?Μ:}WѪ>#yr?RF\%.o(h#v#=קǨէD5^qGXx;N-ԲjLя1W+o|"圵=,U`G BS(?/W3U|Iq‡jƒ{kOp\Ŵi7ZU 5F-|ðu +qT ?̷fj9/^w*r>dQjGutt02o>@ȓq6cլ8wkj <j?&8يr -":,o_=X6 F}xuO+)r$7\qʉO -GPqPbd:X"demV~4ȜKcO2/&V_8"kv/Nw]ǯ8bFRoFը1w)U}r qQv7(j=3}F3F0#":kWB= E/F4g4Z2#NJĞO٨@0dӽ־4EkXnbm|TY#x{jۍ),||9-= eC ~+ЙDcrkʜliCI{5 4n@NbK,ɏ:1g0;J)C>7#E,5%F ~/ziY-{_ٚ ׿bׄ_D96 -Vu0FfB!GG *!cIqg:Ċ򏻷>rk4 -E ~WR't -ytaqf>0\CQgMk F -0+yB>jm= -WHdng_w 6yǽ{GBԱS0(ߒݛktuu[=SfAQ6ՍzS@ TjO}`5-Q> -+̭s@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ?Z -endstream endobj 10 0 obj <>stream -xu@Slt `cba]v볻> AAlQPX{evgl  '?'ϛi!?NFgK N&6liIwV'n292$.uLBR_Jr>4)ٴ6! %fWg^ݦol$>*&?'x$Ӓ!yۖT-f~[c8lI}YP~+t(}%AδA*i@j!! Ic0G᭷rC4}%tG/\)QZM%6a#0 gi~}7'>=.x*A9n}@x{ųka0p'e0q$D/ nmz\]h$ۓG<."VonO`-ӆto겄 g}%u0e/i35ꦭ -;cqvEVG ܊eŲ݄Džs1|3?[2lxb_fƲ0bW@,!J+)ט]lѴ5X&fPvD/[l9Pg'&8-W=8:ʙOzBF^jptx7,gS#]de>+zz <}]`br.k="q'SPѨ=qI"Dcook;:˥oDmꐉKg27L\]f0gIE +YHچ=0or@ t\h4k2* E4X3i]I0mHscۆ2ٛHORˊyoj_kDb@c L1;@#G#tDzPfO橈Psou{ D7cǝObG(wdkb"%\x3<|t~KK{6Ů0;&J$)}*} /Ͻ~w~74y! _]fɱd%KzlpfB~{\y\N,r"A`zoq3Fȅir{ɯ-9yW 4\6sRyu/0F%02Jp 5F6)$6#K|a )Rޡms_呥lB5V茬o_~c떂_b2Tzy nh8r?YtBdE*r6#χ,w4UBiXeǒ箜yF]ʤ01TAI]N-ɻ* 8,01JPkɟہ<0zs vmG+S=._D h `ق\LǿA!0p;@T IpVbztJ>?ܴG"0?6IoA aX43Zepz_Y hKבq,Ok@P' _kh٬\Z=2mlfy%4Cy6h(3n_Lk30[^?yɻ&v˵v;Y+$tSUSvu[ ,zדn9^tԯcn9D6tMg _}Fםi#%L!3R'b$~OϺٺPٱ tEU*H=2s n#Q4Q\-VFI;d^B;?RmzRsߐ@4 ~K*=?m`6P@?7Sͽ <XZ\P)8qR&T.F<Ԋ=;Ty^p5r6S4} -}ԉ@{ww `T}qSjߪ%hU@[ƶXuz\S\P~s}x-uhbϭh*Оjsx klyb8^S\Puu>ѭ-Cae'L";LS4)<7U?uh~T-g-\0Lc4tp3D9tLgsEQC1_qWDkܠ%V( -·B7ʵΒH`BReP;Oo)zۯO^zO߾o& ڛh2a} IejP_ K#r -=3*~I &fS QdKпɗJ PTV7hAg2 -w*. kyUvdl+Wĉޛy.VhI$%F_Vyj`ٸ*ͣrL'<| S/j`1~Z/aMGH,92>fO!gL/X#´ߎ34\haDEnU.đɗG۔_0F^*7 -j}&;ZDnbkRxKF&gKdsksCcoS -.=mOI8F}mݫf7 ,:=1䟽XM I+ ur@ RG5e./=s9d#ɭO:$eQ#D=`g -(y__8u&C?ۋ=^IS簬uf UnɚLyLB!KϖldH^~0` 6pjb>}~S(z~ƋWo~s JzΒsD˯=Syxi8cC[QzsE+inPvL҇)kD;,C Q3ygy hD}ϑp {=*'o_ZX>i˿P4{^q6yj5 9yY62W$rѫ&PfKQLߛDžu?E%y#)2Wsh e2=^j繿=?@ #WCUOY E'7=w@]WzN~.5DCߛA3z^(DR6t('>"i@M^tu4/Y_yA>?ߚ5'LBfB d@R}洄KìN>z3gNz -X#b' !FÛV +چPwa}Ly"$|ǒ͒12S 3C~L6B'o ~f91wŧ ~=e=[o~e34}O ƃ83 -gROT?}=̖ݛr*/@u8 )n+Vcԟ VY#sqm_y0 r,ʯ};L׬]n^[{Q}V˱w]LS9~LʵuJs7a-P]0IX\0oˍ-hV\[R,H- BJS,{CJ\T^/4`Aee\02&d5[uPMm1+>3E &TcE6-)\RC{Rf>wPTMdJ6E -Њl 7I +J[&Ree?SugZ~yO/D@hp9X2oǁV, -V*KQ"$-KP-϶̚L3 g^9->Co^sҥL_OfUdZ -<";iU,_!H!DMQ߶ G7^idz:vwYejF`9sqELjVf՟r{If l5RAEHֽ[)װߒpx^plG'NJo [Vl_ZblKhNƁޕKeyW$7M-Mv],ĞSgl| fґ:y&K)Scg-ȝPesKg]۞e4篜 |ʁhY*=Ym=]X^`^8<cKw{5fa9yKo|vxqWD@4)>W~"YryҚK-ypvbj=GsH)!z3X1|7؋.y`{#f͝yϧ帳0;pyFFsVM`sy_ |fK{0g?sŞW@ -Fz|gGMQ '\qy^%͆ivNϳ#=N3}@?ݠ9"9<\0&[zڵHb`Qy Nh9_ 7T{;CO`qx mO (?& ʀu0^=M{0ZnXUZYh'|^,E#~J -u7p4m55q{rx2(H>@m|粞ʞoKYP$?ܵ?J9e.<K_Wt|Xsu<Ϻɮ_=숮߈,8jjoC̗A ޖ^y~?ޜݱqYuobf 5n2tC ggb<_O{uTT/^~ILΑ|:?O=y8:AϊI{sS<8 29@9jC?>{5ӽ9/ o~r앷.y" c%KsZcx_Rw͚E{G߫ ȇzN6-N|_Syo2T8PNZ!鹲\"ˣq炈1L~kYq~%,3"塯L9K?RzTpvx׷&8<__;1giNmfֵFN\J٭ռˇ%\vo"ye\CI"P,w)*@P> W%y 8<giV} iv۶~DǤ[oXf(>r"{w5̔xn6Xʟ$>F0-ܹzUX;IZ\17WWx^xޙ`8ZVլKRyo`5ҽ|K /pd"ۿkݡ:3(Q ^g?2rm?o3.\M*䟔$)P.xy{{l5nƓP<~u/3e_6?Y/ݔ{5)]Eڤ{ ;~,( f/֦[22k!T\#z*=W2LMyqlIXRf_ClC+[Gݤtѣ{7,h- *ˆeȐb=A>Uֵ)X9vnͅn -W9GW] -={WtW(Lni3۷_JFfȿCLh;,A (?,߫${' ͬX?9u,c'6B؋F% r/ " -,9+fD7njf|rP+l"Wed]q{vdrO!"zCGeqdҘ)/8E8'Yzr)k2p5իOiM%ph(@x]bЍ?wBQ x^SqJwtZ\)_a4cŻx̵/߳% 5 E&FM&Tyr}U匹XvY -|##sMc_ -7)!X\Uc~Ƚg)ʸ"GxYh?% [AN!/#f}~]?AR>0cz-Bwd9ߚ5[ms~fKgzpTX adҾod,6WC"eҾ}ۭۨg{UeR8BmqaϿK.VP$4|˟≯$:.域EOj>7lsrC3,H[^슥EK9*[ZŧgK+ȼSxwDejIYH> >.L_r/v/I ߍc7[0q`u':e='}>Qr"^Ts"^% ^5@mW[=$& - {,Jp#%y>nΛ#>QvD ?Wr:oIM61ŸM$=6 -)*z3i ^[`d(՞?+ZujMr~}fWoFhϕ}{~ -p5Z\$|/];Aeـ"h}?)4pqeJ+>$LZ?; 8F -4BT3~};pMBH J=J<5$4l ֽT2sЬKU-fWgPN`Y;~670%kT?.9AV@=Ig%,$͙4dH OscٗZNyBGD9{ 4xDW眃JQ#̠\ 0ꍲ[5z)+(,Y3I d-ʤ -YW"~dzQ ՜ݨGtzb͕O}[n_.m 07I%@;?^Xg6>u;6!ZO!L0=ΰ0p -f]"D$A:zy#SqE-v;ھd{RO8gG^؛4\nބa` ]-{Q)7bqݎfJ8wdh[VuAf(Jy[kж-$o[;‘r9fVoy-"|96kJj[|r7pZU^0ʒx+6Sn60!wa -0Sb2}g/S˔|\4lJ$ *V~ӷz`=p6A.HC0`c`cKG[KcVl P. ZluPB-ব/8f`1U/)]R8m -糾6jN5Ù&S?D~7o)thʭ[7o$MKRt-$dn]9E)372J֨$jRK:*WEֹ370ӦNQ8omx7sNCG笩 -V<Րiz|8ád7h*.ɼ}NTJϟmG˅pY\xIINN7h&=<œi$&*9iMᢎ?y`n"K- -tqwh (J_6₁m -rp}+\%x=%jLzeUpK3%p)4`<>sNN6xo ڽ&M<2XhB8 S$e Iv+R0#\;o-tGw4#%{?рHq'3!H/uq%`d8 = VE1JjQ`6:$\ DY= 0L+fyZJ#ޖ#ߦD ?0½+\ag~vlذu00_y c &ۇES:̔%Jmw|;ٚ\o1p nQ|l 5lE -P⭭s4#9bbcckyV? *=4Ybs~pSV],&jnC=< ~wЪbVoG0[GUJʏI#m8M*=:{Y+$,쇪<˼w n?}\7 떄0´ V v0'S =on+[jZ߫OZ,@u)U_XΡ(12y>Xx !>_*.>GdgEC۠_ \Lk7G`}ޮ/C77m>W׹DD}UsO8i aF=6?dYcIBY`jD{GvK~rBZv˯.:om\MY,s4[&Q8Ͱ~G2[!Bܟ zqR5~*fy <謹S[5L5h /G$Pw'"ɄzGZ`} 6l2t{$Py%O0( "Q;z$u ] -EVF7h 0!Ÿ -P{'l@EoV%XeϚ@Wt%v#zL#wmlW^du̩tLF=߁ Т}orBÍVoQyu/സ( }`li^=-ۿ/(Ss דZM}٣Iي#P > }ue=QtB\4SaPVK 3ˍ/ WN2rQQ?J1p%: P`o׶ v:kv ݎͦ}[)̱ Ӓ+m6mErTBTJ>ٛ" Y|z5v@wSaI{dbxQprX&Mmn^Tlh8ltv:N8qҨF`n:B4FdRNQmq e(?Yъrxh*}k l~'[Sa'1& 1_ļ5qo_ͻMX/'͛oO˿N(#5ɤpoǦK*LJ\V=SI -?LCTDnvِr(d+oYk*2\nk03sLB!k9$RU>UgLmْr؉ =x4뜙; C}LkxJHOCgK1~ϴrF GR:"$J}YEFB1M34yPi:3xwZ2閬*ϗTFb:GjZI@{1f5.S>.Sm='<mZ*9B=Uڄ\!5iɐ;n09!t5s*Z!Is='.$=O[ ^{\ʄ&nhuh,y!9B%[Ȳ,l#1ds9c28m?BHlTS˞ϳ_ة!G)6(Yd9ڄ1TPnB!{v-fڸYZVٸ|jAl@ʺ4HŖAϫd -\?˚Hծ+HvB͝W\w} d%g~sb9B2mγiʋ>r358q{X@. Ȼ"lO -"煮ACIcSI-bG ~?F̚-u~caXP:$*HZHs&U=Hb!YyǺ.G([Qtdge? ) Ⱥ+=p'RRCSbx0:*E'?TxR*2O4X~G9РLgwp81~(U(x6[>!Ⱦy~4%ooǩ)W{ڊ'cPF+1J^Lg (|;4/=jn1S堡$8{u9B>6B[ND=oqT(1/rТkGm硷u]d!/&l夐&N!zV:7dej5FIrC D*1ȡQ+Ň|q*hyVj+vv0lK8gCsF'/}FR".<}䨬Ȋ?b%7ZJĹB(vT E_}_:%RrOr'ouF2B^D5$\WQ<"xLPVq7E" -w0p:}[`JwgjS='i1wOp`H:֜ACA7{1u;@c{ ,ŸPH\S}FgWk!gX[>`XM7=j(L-:|45?;@Fn?m_OX>pw)/TVPd쏓-m^3|@djsМہ ׅG,&$4fs343̙ʅI`rx_x%yM:CAC 9e E6U(z?xE|$ \c;5ughW"5u",hl|Z#R" =~l6^]ŋl>ww!/2~i8p"O(e_)׹—!'@~;Kk]/}=MD7Gfg$W񯢞G>vD+^X?ֶ4,Q7yHWCmBR]-EOv_HA缏w2^~ C73{u!^]"׎In|A]G"]ؤǧ"B!_K<=k -ro_HR\#>фpBaZ޵T컾! ҒDUo㼿ynAURՈn^]=7^Dčq˝D^yw;k葻܊XRp*wö'IKw:B7#%"zÊ?ZIK]EBֹFL463ʼn,.1gш&dR/Ri `˗f,0A͕<_9)]f$ ՘⿽ck{[2\s;K̚tZcS;+c^QE#)Bgr|Gޒڞ_{n~REP=d|9oJOꞟo!sXEP -X,ݎ8~AK{~TϟB=%[Uf')Fj]oWI<KTN`hac TjC,bJb\v |{r6?UZZ1r;!st U7J<번)0j%!0r1/NzU@uox"tx_`)<^"#@=hթs.='~$ub,bJWPe2y )?CK]vCj{p߭J+Z:B>M0 MiPy`{TvXKakmuYĔ/9H%7rGy`Y6 -n?ܤT?+uYĔ'0VE.?A٭!VR_|lĖ啠eґꞿh(U+'7F ˝RƸw ;Hno=u%+YPY,b'a;X1qf9fl>`8X |"TxDAW# M~[%YPˌĞuYu(K>\g-|UPEQCU'xI) Я~sկb&\C4\p{JCuox)2O ۹sɡuvl?ٯтS}P{/E}PqdjLTj˿tQ*5IAjcc3cޓg0xs{ZyXjtDi{px:Zh{IyO$z=.mjD*=C(yTUI&WiiHBycJ4#7^CXގ! Z!.$TD"e}zۀ{02iCU7ԕ}4KF!}zKT䵞b=S#%?=e7CHGh۱znX)޾V0"Ң=l i'hF_^AQJH#"jx Yޗv5N2E{vl¾Pth½ukH)|q79q8ٴV+.==Wz'*{=/_9UC{*=CU7RcWq@g'zh2jo=-oc"Cb/F+6{I 'y<) ~j9]( 2sj%ǟ uPc\;qڼLyFƖ63KwK/VmuCI<*ɠj'V⚛a07`\17VYjbPctZju+ra>!P.:Ёzqqd sè>K30–=V ]^':JKgۊҦ_*o7OiÞGQ$_U6N0X{U f12BğhkԈߒq*xUB^٭Bn]й=v*Xl6qCI{'7&mn1w6-r?L2fEiR)rx_Xt 6{3gϙ=s9۱+,5Fv_>5Vfɓ ϓA +{I@Ufe`0!mN*c˳Ooi>nJ=7cV<{>^2xW)}[s>TLmF9U7HAKj f7s!G3zJD@υwI^Q(?yĪwEcžy)/HFL6uqb1i#MJswb^fdLC´$A={}>PKOѢuBN.O$;`>m_<+z^MWL$ $n $=sՓ/경[BHP'y/-! t&#yhuKpvLKBϧ{n2OFR)^ғ^)y.(LwXK@ۇ$$=YH{лA0 2L<}Yi)%x?=fW+ ?)y,Oy--scR`53}6'y1;B w9j~'h_9e~sv, ·9DX/sa)NJ{ &46x57CR.]!6H{ U\H{^`#ΉO @Ybw^ňz^Gx'I{9Ix=]5wMa㊇C-etchnIJ}]Pz;4CG/󜇎 4̧|(yE:%gT9~o }ώ'ұy w5znuAe69JoԾsDelܖz#twNJ6<2֗>TkC{Qj*x/N+%3if) =oN5Ac燚hFDvvnGxܶ1]BƱ~A<||Jx@W,6m;uBȦM:.5Rӱ{=$z1Sڇb+{S$odY<{(%E[VQAúUuњ -R^$xUzWƍɹi53SOFmu*?z4ǀ 6m m& k =K(E1N|N33S8y ѥOC/#=v[@^i:&=KeckG(QmvIAx7~" X5 а&N}VVjMq#jQ$0|Ǭ ,,aIVt /-=UR>+aRh8- mB&|Zǒ|9VzaY cA/Ӝ?e5HXOgM#4Ἵr7ZBS=}7=4Z{Z%' ~I-!8zևzC&pvr^S$9^7l'OĮ}K-$o&f'|vfi }y+zۗu"Y7]cՁ* -R+ -3n;8b2sTc+n,J'q*/'gOKxPSBLHP6y?~* kNR%v}}F,*΋';hۜYHȻdIĐ'؟6 ѕmF3t*oOUcpédtKߨZ;I(i _ÈX9+:CCz5k`'t$ hO٪bxz -?aR/x<#O~Ѹ@k>s)f_Gg -'9/;emYCipPv9Ƀuk=ك^#C%'fFc){NǂȢ74J1iBi?`Zx5+72=ܝOH} -|t0=者!N~f" '@ajNW&GCO]'UC/V֧KVu=csӹeJ{$A';%iPUcꓤ^@nVu#Jq_Ubabe4dCT>-Z0@F De=OU0tϥ >L~W_ZSR6*yPgToc1},y·1&| O~yrG~k.X8"^!Gc6qx쒔ϭ^;Fx%x,Hgq~`k绛jlPl+=hUk3~fūUqYpv4&ׄT04ـG KWK_gP< ~_QE:E)v9G=h[QE+2,suur6D4ߎ ~a|`N"XU&kvEGj*<|]6Xm 5.fc -:Bl8,@gzDV@|CA=o9m"Fzuݎcml -?bI/fa\G0: -=ml 0A\3VSbO9=ΪҨ/+Ҽ>|3yji&K= h xVܨO6'.:"tF!t؛W~N**zSy 8ǘyN OӖޖ[yq!>&twzfC& dk z?Q}5gb[:,L(?ܓؗ.<&4Y>?}7wS=Ԃ>m7 [L[LQ`MY,:EC=Wqy@͆}͵mWY\b;&,|NW|]}z#X(V,V+ 2<~;`C;E] kmoP8{2`j(7tiTrNnMǓM.gJS0C9o@5v/W?ʁ8|GhG>^ZM^^L5S$8LanNf`~9sۂ."rm[帬FqG♦_;-ZkA.P|I/43SwbC`d|a48XgtugPxqDB̕&bsDplacP` Јð5|LLNOE-^3o6CFP | -qx7љυ؜M/.s9^NEnzM{aS~?n,S -?k FŃ8޷5/=^+&7ͣ=Зގ85!!t)}O7Hr9 `7ӗ+k8 { {fym vJsӸUs3NۭjEumR=tBmAoZ_]bx^`vUNQ/fi莦\8/?,?r,#_Zw gPXwQbn^M˸v4r9[7I)0JN lM*fԲ4 ,hr X`: @!;k Kk>-ׇ%fEЯ ;&MEυ&U$!g)KTFd 4Qc-4<(Qf1L]2@colU.PXW;cdmu*<{#ծm7SV -6ldA"0Z@T c7Eþ;Ij0tVlo}{ZT_gIÝ4ol~!M ^ǭ:CZU#{sUiIѬztVCCKMYse&OeaM՚ wj{S _֦ŭR(hm \=ꮥuX1m}*Gk\8pF[ έ*Mm/T҃A{Oǰ픕 3 ;gx8' "=FZ~[ZUS(`_5o6@/kpJ?6'auJi0,{+}*;>1 i(tC!2lF4G* /{A@l1R3-Å$~X}E6&UwڐstyZG&/.'VFU{hs[skRje:d4[. 'sfJ5\@Q.yVOaA`cqY-D찶,e<#ެbVe5N,Ӏt{RNM ryPPho֌llm,7"r$g4k~ {2:#7B{B0-ưe ~{~kݝ ;ҿyM7 -VcG'ŷίoܑXac/c?TwBK΁îau,kO~†=^`i3̚_=z i,q[J͏܄zμ{ԸI7v -\t7\o -Z˥@LWXPH-> \nq}N*1>voP4F|b<qVl0_K[2%Eo} hW2Vf{wE"osǥWhj߸H.+צ5pb㕃c۔.7(!,W.ɘ74=TٷmemxW5% K*ו{]˂O '̬/B$w"O.-Ttvs,`02ksv~]0tG?!x2[ ˪Xlu: ⚧})2*QM?iYE=<ֈ~.炸`8&HZެ}'(:!RW7ScގPmACC ~i  ߪݿef;Co5~4߈oO'}ɫtک^z]]=Z @oޫW)@-D }d/)hݴۮ[7 -}O OjS &<Η}i_?x -\kC3 mzƉu͡ANPy=`FA{'v>F`kANZ ol -{i+b}~a芯0z4 XL 4 zNZ{Ιл|ױAW?l -::PU4% `tiFcObE##:Վx'w(㖔=*Pϫ -%ب D}Pv1>oWLf{>:k)Hyѵ+(y7qk}>rN͜4RgsneR*E?ˢɟU+'6wDZI @di=19Q W8E=r=4,_>2.oiV6UxvHۀѱ{:~3p*s!g5efgX=9B>/hws9}KV6Uyo>g?_plA#5J (&ōG?ߍ5jЊpvТC1APwmO .@ş `Q)rGAi44\, =IСæ)գC7Zzr?Q;kHҜ.R7ߑ/!N)dg+ީ @ ċcgo"4#(9:l&;SͰ(nBsD8v#@ՑKqMY8<)rBbWvv57]'3=]LJ#,j!{T[wnt|\JsOTއ~|( u)'Mꯔ6Q'42B¯}m%{ϕopk`T6+L)$I'nZ L^ݨPPHAJUv#mN7i4o?xXoJvgf%T -) -oAt9Ek-̈́yЅPoa ?Lm* Ͽm`w &^\tMu0Z^1c:MAp{uP'-/|Nge'_ (>Oa"O]8`W6XCUDᘏs.t=?L9ЙsO[_yxn6f,Y_v*ϮRAK3sΥFcAB#O`נ %‹ϱlOÆ'{|5]:e[(ij\"0DSh1?0tFIom`:tj@"aVmWѲ % *sz9Vp-sV{I~Ey9=P[ , -K6[Bw;5 sp칀[S"/fbbd!͡ILÔ=38'T"Dp9zxr+?sran^!pkrMɆIfW8SZls)}4 Es]2%w3RtA{_iSc:#/Dy70f_rP` QwPhm^~i]0Iy.rQ;h3o/fϝ7oY"fxUAdlu}gi^/>zGz D -uc9@ sjֆ^Yҟ, 9/_"[ `5E/xT#%Q+{ eyfJ -t!#qEi)q < {Rj!<\VLks_AK#}y1םakJEMʑy.ܫL|e ^sШVF kֵjXFuѕاs4*Dr2&G_ӴaVpeַ"\jش[g@j"DpMs^Clj;̠C'?~DZ7<. %{\=xD -7?Kɑ; P}r.0";]őPm -ՂБrAK$| -DvKQQ|}N|yzW29_=0[#sif1W炓&z %}E{30B d<C<ڿK DO5:_=Z @$ڏK6sdhRmQ|iA۵Y7\ -^ %XiN{VzKD!]?t97<R甏 i ڝnɥ@Sy٩J8N6Aݴ6K[. jtlzO\󖷨ΰ}AZ]KL~8E&l|F6(VE]691sKr`sO<ڨƲ7ny ߢrpVu$O/"Sjxz{dr6TEL[dʁ^: EvK3ٵ~7)v:C&IrFm*_ȕW5KcCFahdH#6tjQ@=ϙOvKڨ>/.D< +9n%'z.>?0&z^]2c.'Dd -Y#9XVlTpAn\dLΓJwyځ,LG zAq#SVqFaLG%T , -`~ m:ױ:b'}[ e.Gb;c])Tϳ.4zY&Dϱ]%'--#a;{| mv`6t9aqCr.4^ɶTϳ6Y&HsݫBÉg -6L&a<  =?T_Vva< ;$mF(MAmE 0sΠUF*ժj@dgjz)lA})с=@w3fCT2'c@'2y]QC34`)96tZ0 `9sX yz080,tc4<筠,ӪnaЗaô/xdP-O^d@]{wߝPՋb߬0*'m -?*8fg08&)|z4l#eß -'W]-V}M/8_a!R=_Ra\cyTwF q _cYt;XTw>J7ާ-vT?O65caX(`ig·m@HV:^ۅBg\?,1A'h'Wxѭi3vc=Z':y WuЕB7 4~i]юЊyK)z/t<_aCz5G|)jn Y׳jX^n^7s%\(1Y~2ԅVoE:_\%Tׇ6^0s箽{"xt{w p_o} j&u%gr}<kVHzff̲q:fwdzX -9;+.o'yZ0\0{Nw"Jn|`^u.nmXJZ Bas!Eziy9xIy<'@>Ȃ'Eٰ8::x @vJ EeS@Wx.Xz sdKجNe0 -yI͕$X^dS]CvҁnC)GKeBbDwzӾ@9:-lqF~8"vP.'$:z0E묵S\Η9)֔(h?{V#8?R$)"Ɣ}B:dUk#1괅2@! -٩$>հ"gy[Lt9,s -^o͡NZbe'+WL0D^_a^0k~1G9V*SX0Nt=Csox=<s Duݤ乼۽5 -t|N6Ƿޟ@'0iS3;tqZl6"zOƆnCGT$n2?E4$+6-m)qk4&F+^9|@ @ @ @ BMV8"XB r@NqTu'}Hˀ@țWwi@0SRv!9`q @șk/(t B |VvyVB7YA F[}(BY (Uم@ ̃ٓ(t3~B pP@ `59RW@ UL̈ +yٹTBZCw}\w<Ӂ +.=|؅wt6$ܼJ#Lȳɴe NwTS"/{fX?c#3*'F#y haXtk3eҌ=违qHɉȹ|^>j4Xg~"7uKh俜=zXnz=ms˦LI9;S;F&tsrBR6ys9N]A0z|9`gS SN7H0a9mxʢg\u >vttI9c'=RؾK3l'D jo%d:~'7Yܜmߣ\nNN{Wzw6o^k6>r^ -$I'^\UO۟ >z[_p^Lrc[q){]f۩Xwv:_?akԉ^eYe}n[9GW|X0f]8lү9;{ -e؞H q A&v؅29mz7v?|ۘy׸l,|4gnjyܬ6H}1f Z#U\uoi.n3 -.KȻb7鱥gF{L}=fwL:5,/fńcIGOE6뒽 Nj ic/PLp*pcnW^wp:'q힌=N.I΋`1+nyBИُ=vm\9yc {NaFo¢ML忞丛wXtܐ}a-W q똅XJu}sbv;~¨_a"GXfiXug3~o {ߴk}ÄG+Lrb iلc Tz>.,RV8mL29CNyn,,cl,stgȐshzX7f /sy.ȍIJ0--\)iщXsG|.< cte$Ffcbyi(yh=_2"~#zIGъ"Hx$(:?=8w6b)3BOr9 9XBsʤ/qޠكsy)Ժ>$UYEi|rg_LN&BʁM@90_XNV?m\XZ`Qx}W w|Nl#\^`ڜ7i'"hi[}=pww {7OK: ;͗L?|rQܬs(#oN,:9JUX,--!iSGnHFy}اpsvN-UV_g w9%_gPv!~`4|:ӇYASy@b") r\tғF!ϩ|Ϲ?Ff -e/rF)dXˀ!WoSFB< =np }e] 1%F[A%tvUa1Pek6mo䎯R:&z#~OCwŗ@S O~)34?'y3kC[Zۧ -s0sA^ ,q=rFk - rs6+/iŁ) (}݄q6̜i-!ob`s}#'!s8D DI}-pϫ(>>+5ߔun;MV%e{=ߗqr@1oRqof(ݧskqJw|1K4)DE>Ys3=~P)83o?w&"ϣ7} -(ƌN& fOMsϑBg8ܦsrlMoyw7W;{W%8:PI+w';{7:.N4puUZanvvN43cqaf>\m3D/x|(C]|%ˡvNcGyn=rr`m=N9(vth;cQMΙN{|G>}3p0o{JnAq|fi[Q>՘ssZD.r 7/rلcHHa~J K+7申y<:H/,[hv75X$|r~t'=i.eM$isP^ǔ4GF] -͑o4g}p6,N#ZnY,l -<} !?-Hs[~ -4{1BW|UU9ؔߢϟ\E.#DKB>+*B8G섪ԬO`˒,\)/g/)\9'7*O63ԕNi ysM -I{ˑVw9.Qo#p.>/r3iVʞT1{|¥bH}_Wڜ{\g#.̖ ǎ9gS ¥U`oȻָ57@Iȓqp,~MCx<EQc,;2,4mpSK791cdBg -N,-)4T?ޚgI ۊ0aN\Pa_%WKbߜsz4,\t%}NaO+0KD➛0iHT?sWS|/N1WK tdžD:x/(~i˥Uc&e3ǃao#{/r[)qúJ|m17+qjBin - ڸV0~sI'[Gn؅c' -O:YWğT6/YRslƉ3a1DZJzmwJvq<$bE\prr`˒ICvgp~ -rD|^ǯ+ةsN'eo?ΪY&Hٹq9\<鴍&cA'Y5?A@l1Q[EP.>;1;~nnLZ$}% ߽M0N6~=..Hjb -߇,G*0jѴp9y7ׯWy<o)Q7i{J@D=_Q՞w/:rx깗R":i#|'wgi}ϻ7ʺ*=WCyEx>f=0{O9J|K۸4o9wf|.o棥zO:b$ϙfI>s!&1A9q=`7\=pڋPS-1ϻL( -gPw ~hTu9nzXu?zΊ lA3++[%?d.=@,gy| -߾ljZBk:9".RR<5E:=}=gY^C 9}KsM[`d9ЛbPsBl0ϛ˕V90' ;텈7zC y<ϝkMC y^IVgx/ l=g|s.e<-RNwZÆR=l` B}`+Msf+Xp;4D=O[QCl=g_0yx^ʄYyHwڀȋuk%x5g%]Uu!yI+8ǣ#yN/_Už5˟W&>g.N^x^ܰ/A6Nu'PzXO-_~}譎ƍ -/P#(sɈ[Ht}DC٩qqė"KХEƯ萃(9_:S$y.(%IHsA\i<=_.ayθ""!ϷUEz.T+^hrx^.-!;M B=,!>P%5< ;@Sў|% /xZ+$'>(7rxΝ'?$'V -Xyv:n>hN^\3vHM -?4g(#T|d'J#˪|1Nx,^{>N\4_X| ]=wa-ZYz;Zc"䍎0mpNT1/QܥZHE^|?K_I]+9A g3wΔSf]={IhEҙQ3o9L0d|8q&3OƝK1c6?s,7x5s/??5Y;w+~sƯ6nT>s.lڴ~iGk:^@g&| #ӛ>K}_L-}0ӗI>_S0+5*%|3eƍ>'Vl$?N 5b$V-Jzn cwsw$'VV;yP9\Mmɸ\SZwYp=wQQNΛNq*Ϲd\uɉI #~uu-C]<rbr=o~UrbeEpҮ%V:/d Vʎk6j\]o)Rd޸??UP

' 0+.)fggIz;l蟏o#򕇢=O__wS+&{+ / -@š _} ~_)`Y_YyRWH8=⺈$hϿ(|C<,[q^(9&L>~ِ-=+Zt30cgmAyi"[h?[Q RRh?L.x6=E -[&@Mcziضi׺KS!YH"y˻t$4 CJ0ϗ$$M0;*d jw_- t)Žr(K=7 ?65KMSנM=}*I@=oq37=jNOGh? h \:iI39ch}HQo,RύK =d.IASǭ@e}>\}yOxn-ȓBxõ{G1h4d+bIe_&&k+֚#AwޓzNZOcx5\ kjctԩcǞ- spRy.%J=~Sm)-ܴXY(9L# A59&k8;-.O(X{腋.vo(7h/~Ve>S}BU/tS}4hb`ػXNf{q'i -G\$t(]K=+91%eR#PQ`q陟Y'Xř\깮OoShw> -/clA_q,CўXy}$NUU=Gt` <dEP!G#HQӜ.#@x+WBʹr5jXtWOW LF,'!mHYK%^ =b~eAGs"gمzpvjj ~J(/؉+̜9Ϝys/et"q֜ydĘ5wygJ#VV]|6bm25?]6VwEA2͛>?/oię+ERO4~i%k~>}ys8z{M$[,9|}||8}ڜ< i s9bn5tX0+@?_#ޯY/|'Gd)3nGH1{GIŊ k_*\6"lE50P jc38XHI@XLB#R@^,}F6#zǙd3rmޕA*/el&ϥ*@wxJZbaq_(&Yjhu㜌[-}}JqL %'Xyy?Hm{N edoMr\ܖq'{Z"AΛb.$qtyu#L Wt{.H=' -z)2q]: b\ڀ -ey6z"*v1$sǸiʦzFK$_6[WWUF <\ĈLPZϟ:kcbqiIk:2_ Qy.sUc/|-_*υᛯ1\qi~O6Dz8 kZ{SxT 'z ezk96O4]ii;@7AVqĠ\8շ%.Y*υG;6#7VPkɝ!?5%\8ǟVyΥpy[hvatW鷯\ -^tc\8׌?#;)x-z7ѨƹuM=Z=s.%gP[/ϸ:ؽO>ݻu;rr(׷x|U0qUfEwx{SPkC(~zҌ(2w5#NfJ[뱍ߖ=%_㷌ܫ3[ *;'=C3fFjϣG|8;#[GSNd (rWSe)yTv$32_I'!Hʚ0䫸'JF4PojTvNZ#,2_?;/-ԗXGv3;얳|JNU5~< UE#UU&)x -)<VYY?}5Ja%~!ߙߣFˊ?;$SQdunEW !ſ=fQt==I[;Ptɋ+"Q7>*۪0{]\ ?.R ȋ^ (Tuſc.@az@?ēNZ`:EGlsvvZCQh]vav|W|k>*'nQyE4_bPoNZXsE_>GTbڠr%MXv "{Mex\bK%/ C*YX Lq2OTKTv msۂ݅LYVF8;a!ߡނJ_LF{2Pސ/Dv%;[\7DӦFeXQdM4AEnDeWc Dɩ* -7do}ҭމIJ$[,)o $;"VkhHТ@(RWt"\]22I"#FZndk%_`x,E:E܋^o `xb s9@5‹;l$Ѡ:!K(Y (Ryj2?]$a4!ˊԆ~y?A:։FuD 1hu\b~5G?7茰hЋ|PDmPvT1x8}"N-0]$ %}< ŽYQĒgAS&$J.7SG3(uVi`"3.`h#Eb찫jkf`}m>ȲJ`9chn[/Vb_7> *؇FSrv~CBiXm;#1r ->+)b/A-yjh(̨.oҚ܏M-$"c;4?e '|egP􎷤s -ꦦf>R7Yh^ u*}@hG}t|nƚ'=x 9rgitR J.Vo¬J"]m757N3@Ӫ~w}1Rv(7BzZmվ#uT0?|cqJ<Nt6gvdS~Ҷ^y%c^t?lHzV[لig3Qi&s<<@|b Mȣ(1jK|8Hښ6HEQ!7'R:tk0R77D,Hn7ui=2* 2& WLOoK#V'~J1;Q/ڐҗZʛtEOAHO,V`9xwJ@_nsR%o|j{N=ZV^Rpnմ{t6m'SwX^w7}0س̤ݮ:;M s9lX eFvTCGD!F9nE yIM?Q6𸸹_KR%յmk2۸*[Rby;t$"h&.^3WZE@%uֶ5>Iq$%=e$]"V]I}]_grOerZ `4P "=J5#+O&X*"59TSs/WԿdG] x-aAm牘%sVl[Կd~ZNcԉz4ń)ZV-DžB x.mT3p1%=/Wq& -<]9]%Gϒ)~6_=+^tc@&püp7!X -AE/4>i.9Tz$H}!_q۳B=bJJ/ ~ LMTh /iPc*C?DKHVZr 5MўG.xؑ(rP0w씉'?M_DZ-r!f"T5:D{u.)̒bY5)Rk`O8THB9'os57lāN(l&r';D{\ɺW:Z -oxZy1H0-e›4]'vJ#XbŌUFh1ze\LU|{zi'j3FB;qtn=YP*ў݄#_.e'oz`?FKd\LU|GNVR0yQch~? ,m}UFVZrX!oIx^6E#b( L_l2K8p=:QpPG**#~~u@\"f%oiO&lf6_  HIUA2Ly>Q`7~ׯ]ꦿ~-,Lzr?~d -)hDoo N{ Vuy"O?yY-#cCW8] v|O=# \>)Gnt4L w60aa]˶9a,l-'YBr԰e-EK!²V$%G kao„EMao35MjFr:.yi>:TKl2))8iP_ cGB4ptnD09FCCi%j}#3rQ-}( w$4AӮAŏAöD>>Ͼ{K$y|F>Zs=!y曓{ 9t! ,!Gо&wЁimV:#Audgu 8%\uP' &bƝ~${wi,wXcizBhŘr(.]0 -1;Xj|1kj\`J`/$78ǐ[|@  -B*fΜV9-]pY:Uq9%P[әP Rkk&(r8{Ջ@{?5$b6>=}|Y޷j^yٮ\%e> -4aBg$)ZeeM*3l('?e-giy#.j8t?G @+HVWRJ.҉44ODHN',wpM.keYO/-Hw zƖ&P{< +K=zKQ]<`i+S_W-~n2_133i_SDDE{f_p!9@X=[XC(!9&uҩS2:uo] ҥx}ޗt C9d"Ős%; -&yhMS}z6]343췎@AA:ŒAA n͞߼!_1zb ULb&]'8pHWAC'pyCGIw45ȾYF}?U06J6zql'RnP7ٜ7 -잖{0Rzn+ϕyy6h)6_}uj.TF E XxU]ZXժxpZOu&Cu56;9;_h׬8Fjeggq?FykR{\e8_Ȼɇ}٭3B;zKxy[Sh/]KWjܛV/JA\Cr^zs"6~yfB!;z^9;չ7W.2 psvm᡼ vte3+UՋf܎J-ɫ4MnMߧes̼q?[z*:kyDէwDN&\`ʇ% AM.s >?u׀Gp<(~.4MKLo:=H GecՠN /?`$C 4bݷ8:#֧ev&NֺӋkZQEOE-' @tGD -C&=of9/&ya PkٕO&|\JsŖ`\笘Mh%oD֞o --v<%,3)1%%-5#v<|Ac/C#^ٴh>:s7G3A7k ڊ axA x|A4Q^'#$9+ۀs֟;I _O绵` +V}]MHզ# .@559i^+ |ޒ*A7іOyj++<>#"ǂƀQ1ž3v[>}wD=9-oF3BC ,=OtBaf$#0BF}`kT' ahϿϭFuaU4=-7p't7 8OA۬o%_Cas{@z<Բ/4xΉCJf8}DON;F1Es{/!cĔ]jJ~ 4V<g",|?)C'I,'9#8@Kc#y@UD=O]jV70? է\ -7w)S.CF*1HlE.4nz1lQ~l?3){Z򨟮,'.Uo>ܻ -x˧0T#Ehz+_'9}WNVCp91b|j6y?[Ш9)t4rMt|A>}$Pm;E%-ڢHѥ& 3a͖"ȝfɦv?DM z!Y I6 kչd7 p4w]2MCc;ExDAsb/@ -9U嫅hI|Ie kC%x^ vyE֎ugScW>loZv=5lM^Jl-(uyk { OTs~Į˭ -[ .L \q&][?gj y}ycy陭9 ξd772Pϋsh%zBR"rE%=О yT7Da!w%xz,Yȹcޒg,d?DL8k6u(?!K+ݡ'Wľ"l`h*!YL1(d\IVg 3bI{]zrzϞ-zZbA'&ޞvO} ʮ,:[[zE;qug<I'Nb߂zhQfW N=¿xq'oO i[ff)-3v2VjmEf})PO^<wNKĽF\ 8%&/+?DUsՔ9w[PCN=#0a `{1ڒ&_lCN<$Tx#`4x?j{G -m#hKZ84OmlUkL0cҟFN$(=lPԎlLܵ}uRZuU>${ &-m-M + `_gb:vjо#k ._o(P\5h`M[CsIy5@D`=h&4@UIX3`?{"-ĽDP#0 4@O+RC _~rz3ɟF-,{ϗ"s{ʄ'"}t%mIntDYT`+Kr-M+H(9o.aa *xn&9a堿܇jD<Οm-)hEoH' + -Apl1lu\=:=+E9>H)*υ{{.h\O[Uo_FsL͓~zhG? \.4F nOW5`YU_8q)u%ZFdJU;B߱*(Ï&A?ឧ&W -};?tqJC|}:_K9ϴ؂ebU ̱-ڔ %wN1KT{y.*"#DabyٖRg=ʈ<j|1!V\4)ج1aJўk*8WDF 1KvBr;y,Sa#œ\ImlznNaɗ}Wxv yыFe%Hˍ2QPouAK> 9p ާtU$+Ms hgիў3#[ʡ=_sM@CEUښh/ -Qa;6k3 Hd=.9FB֣UhY?bs)5U@$֞SĝZU@@ z>R=hyEsUIJ: y]hٿ\Q˲t֟~huNEϑǖP̵Kn NϙɻQϯ!AP\oa45.QM_l }= ʜZhϑ.KeYzvh=Ce(jt5ۻk߱yu BQ[)F]5=Rmϗ$L4-_z$Rԯaa>~ú216l~OjMPP󔕵utO -I@=oq3/X#ŨQLA{|y1Zݰ|o60֔yzY%FMg,~K7gS^!\ Zz)Fhpo;!vO," PS`AӏC Ig)b<Et-4Po-x`CIϱct=Jq;\ф؉D[j֮ۻtpi;f98<Zo6rhRȒꚞQc[A9fkcY5џ qy -Ghu-.A-*-GCPQ}çk&AP]4R2VM=Bܨun uzf"s.[ jga &DAkNՠo\k^kbS%s?VӒdWl笃Z|C04?aW;"hurPs^s~4I(?Gb֤0#[@UuKA_9qulҰ:@wA}W90e5tFfh"sXb9#H\f}NV/C(yzRkjy=#.Pcy$]]k<ϣ!45H d9q۠MTg'(9++}w`m[8*|g\6=pڛ+R kC=VRj 삄IJ%~OWbfRtlϒXvc?N=j &q>2\E᪽Rʠ(Dlyspϑpjm ˫ΔM+}\c"ِ܊hϿnHS} zxu٣z<-t.3l'TsZ2+=*ϩhoÄ4lhcB=WoJ1<"=9)}#tRrTSmʦLv{JRTSm~2σ!*ϩHϗU:>r<"re;'Mo:ȋ00Q2|<7` E(D #G;΃xz"9/8rF;n4)՛1ʃ|FGۃM1g>4;jEϨi\jc}8邆]}83#e83{i"~$*2Ke(nLd#6iL!5!>$nt֕K4Q4jPRB!Fk`Ӊt8#`~9ɴw޵Ɲ ,B>m(/ؑOy%Zk:TlK*Ÿ#ZvYʯ-jK2\}H f4 w8p(HHvGblu˶oFU uԮqt1y}hwvOe, ).@i'FUDT~Xm&u=n04qw&I89tim/ GX'*3Q'& RT/^-(LR ǹ}Ss]:^]txhumThYlRcJB)Pgpss=H{'xZ|\oI;%/mtr9 se 9?:GE7Z3$2_޼s˴ WQJ{˩Yۜ r# -f5">_%Lk Gs%㹧J}7O3wNpVݷz&nx}zk,CWMYYͫh"r"6ZF597!JXx;39iqN6t,DO7Q4 -endstream endobj 7 0 obj <> endobj 8 0 obj <> endobj 9 0 obj <>stream -Hbd`af`ddTv M-K-MuI,ILaa`a!#vgOKVfF&!I@{O}^ѨilGj.X^93Q:."5N>.%-0ǚȻG*xf\kvKo\?e*s,_c1[_RbR1GQRdQ{Oݔ2¦r>pe ǖ+_^a|낙̞2g<_7_77@ r_7 -endstream endobj 6 0 obj <> endobj 2 0 obj <>stream -x\vș>'%ڗ[w/ -HKK=yP(9IX$X˿U8ɛZmwt]å8ۜǓ7┟~=޻SJRzj++nN?>y1I/}'L~ӏ'W}5=z0^/1.r 7Ygߦ\LVBN˻ugqv[~%?v<=Tr}>-fz<8l,D:XnjJF˟p&'b]p|!w5&l2>аr `g~Vk_;I'D DkH˵ 99chW/"aob'4ݢu46~sU#Ȳ0RږcsH"le܏ 3zȚIWv6ͦbf fŹS@{D -p7ٳ:%pE6y `RetyÁgUėg9CosEfl~W]N8cݸr|³|V ŞQ! ^Q@oCZ68 -´VM*yA;܄j]8a Xm墜7_7xi' C=N(!0吘UXߍ\=mpJt^k^%S*?:nqdG&쾆dŘ'Z!@nvr0IR4' /e#ME4fOeNe&l81x/>Y G4%VWÈ9kD>jf¾:U|.OI-QlƷi]-D 2 W-z7\<dA<^>U7:hK -2F$:}pI%q6 Y NUtNAQx[֘9Hb.AFVɎf?ƿ<*D -yяS2vռ%&٠ճ"y1]nun_EUٳJ"t.)pTO dR&XQd[8KC&C"xU ~l&qSe|I6fj| -y& PfT睩S+ͺ Ō5ʚ6渚,9'8gMʾn]ɇrnzf8q#$Udsfwu#t.u+|r<>7Y7'>oZ6 OENG=(ZvcHPm%u4rnӎM&MB>UuX凷_ryϛ \L!rϣ߽Sz??]Ŀ?iTo - -t.mlpNute(Tn6=t?Su nl~QV2K!퍦 -^[7h 2Gɵڼ߿zeG! yfՉ۟UxQ%OlZt8ďu4*/ag"|pl]_f4nNNd*gM`悷߽mt`NZ-7 -dU#:V{GzpR:`| jfp59 Ô! h."/JQ=TCyB{FPZij#>RVhA=": -y y!ԇٔ댽i)g)`iL^ERs_p%kb -!ZLAcOJ),ewoFd,(GZ".pZGP6i3P``%]h k$ѐ msÌRF%"|XE&\1HmJT9NِOIjd>9흉(hi9Pa4 -KhCbDUg} PXzoM!"hts[D`\;8cz:v\M}!uIt=4C( t+4hCN{d/rca %0y+1HV"XիF 2R1n:#5[6&!4x:W?PL  -[]l}籴j -ȴBj^m<|㹊kv2]|fW{oS?!|S;OSBg^qv6yj""W;xϳ%йtBi|sV+ T#fF>VAv3Ͳ -uGmͳ/-EFeID#el樅:p)8NȬԀb ]{X) C C /qb?-K^(GM鉞s]+Yx#JaNnUuɫhÃl5hS)3z*VtRry<5x`C42RWƛ}P +Y\J2A-]+xdPy' NSwu/- ˜鄔ș;왳e(*ZAn[gC4RAj\K6t\bqnn7sSt2RrJvB0Y,Z?:u4vC衔9o | -9d5AAedOWViOe̐RMyQktZ^x]x20!kH| -VcTpeC9|rv._~#>_1AJ.t</u$j$/AmT*-:RA;JNyU(G\luXk*족͹h)AV<2]JmDosVh V,#,$1%H5Gk`4Q}`CMЍ=Pgլg4vL$,*Vr7:ڧ֧ßhin(kD^!ODKmjusPSq/,?A} 5uF0!83^>C ';O3.?Բ,kHYq}x˖^w! - VNiBurh.']rSF xǨ9' θ,A|yJG* s]3bD -hk{Î)\cСA֦`g&SٞV[YmP$s6A5GR='S OĈc*Kcɶ/ P\=7X>]O+&xk7$1ey꾈AZYm 5%) .EQwa"G:b$T -ɨ5Ϣ,'!@Nc[oѳDFapLD&+lVcA`AiNR2:U_^AC;j~ XuXLE!Oi}j -h%Y".v@!"yx0?ɟwܽ|73%qy3; -.y0|9M%tnFԫ޾P  jh"Q:'9-{&;/W(E /c<BYbVX 4UeAFQvg}RsfHY05UMoMhw -]4bv \ۗpy f*Gskz=>$@<[R)ۼMyjچUry-6xc\w`a]h*#Ns'u֍?~^Jy@ٱJ -ud 'Ʌ7yu%{)-Rxx.R*~5YS@h ?:;d4+y6CO$IHÇi P6*;@u -:IA&xr j ; "Fl`HN dhoCWѴ#"v0+:}B\V2oCQ0)N}8I*OtwK.e8Il#M -$Oyr"w{3faptI $* 3X7 \i.\>ME1GRhۼ, H`5+p,e6D$)4sY\38b/ė_[\PTftc&P8cb,)^H [r,YA9Ąfchwqdt@DбX/Y˻G3KmDdҥvv"u?l㢣 Ny@oJ`MLjrjqk}x)}{Ajuf@Id{P3X6Ѽ-#WxbOGD¨ލ|xF%ct4}KmXs+oGT8[]($3jka`p% .v{@S% C^#7/]W#B)DN1MGgGG$>x9O4%#c]1| *j}]ot+;ê0jLHrё{(PI|T^@*$c&@F ]Yő;pUpHHu\JѳT:}4,?vwvlgȟ0@O;Ge@< -_ +\5p<kuk8UzwHQ{kauDр -q,]L1*tĐH`HRy1n20W4aʀeNpwjʆ#}Ngu><_y{SqTv]8|p2|/W3Zju~Zi'WRXndo鯚^ ϛ$W<ʙ z[def=(R'*AxyTQޢu6JQD=ʎ<*=Aշ(!FűU9 e@q!]aczuOϢe'U:3F%W90FF<҅SaA4*0۵) yd#jIԴs Aj_뚮 b$x_d,x_=.z] q}^cie~גޑŚ,w\WuԶY/.VPV$Kk{Uy_IX;)>֥0{+婣5k ;eAeSm{۞ Cx #>;cʸN Wz;Ǩ'̤aM:eAOtMc@(54*Y]Љ/m6)LQIؑܖٜ<KMx7<_j9.K/FTRxX\]x͗GTtI@c de%&e3A&e4-1[LQǥ'S fk$@}y}OG];/ë{rRi`PA5"Pj20D|%@/01QdIB!ȉɜ>/ExtGState<>/Font<>/ProcSet[/PDF/Text]>> endobj 82 0 obj <>stream -HW ?-ζZVxSt -<;qp -N=_x9G^ ὕ8G&&u1 /؍QSsm%1\a@ٳ5;HNj2|\!M<.ukO֔ (Qj.ےXS*'$= :%_#'!&̕U+q%$(""Zx}GAhNGe7<7@]CDa(&sĚ1P~{98BH15q >_ iD2AHtQ]; -ڔxmc̿t^Yx,~ODh -endstream endobj 81 0 obj <>stream -HV  }Yr) S~$> endobj 111 0 obj <> endobj 112 0 obj <> endobj 113 0 obj <>stream -x{@83׆fɌ-QƆ{/`,(H[84E1 -+ bn-11>ϒK<͗@v;sϹsG5nĩT*;__ǦcɺԮLHܒ9-MK=يkRYvtѧacf,^ca4uDgooq˼/ ry\@/_[w@ܜy- \>cׯ`CZl阩{uqrqݹ\OcΚ>p}~\ng q!Pn7Íq Dn7MqӹLΖfq9\n78GnT*-uV59n=q98#\)׌kε,\+5gũ8 ׆k˵suFp#΃Vra\8r\bX.r \2epT DT**EJS2ThL4UFY4lqoş: -!b ?6uhzٸfGǵ8Zjկo\fq;m}np;wq> S.wTw9ߵ}W}t_+=Bz)snϤ?qt^N~OR>gR]}Gu[կE~g1Ӏihcmfc7uA'^5C =4XaW קG.p˚u`D[4%SH9kˁEKeAJ@ 1ڗH$~S`.#@~>&wńX 1)1@1CZ9Y ]%A^S_ƞ)sF%F+!m/vIxM/!tFRne=:lL[Xm,)V).jP9ÃGçL&Q21 &yP(ZPvx$y)%1SdyCKW̿OG/׌7!FoI IRSBJJJybJ|J#h!!!! Z2]@{O2|n$8 p~&(RS̽1UMp .xM*>hq;{uֱ-q#q?2X1|阀ytuwn[]6M)P['CC|Ӳ3W53fr -NiO)N]A*ڣ^dU=q,{l:^䡷1hlb类ڄ/5G<|ׂK~޶ f߂, /RRr0HFq,.btX1dV\.@la,"S5xKd6~;9]8fQiE|NR(V -ainU,mHȶ7~)d !*6*jz/k:WuQ#4MY nvZ?מM9_[ASer:UiiUi5u1i@2 -EN0%گ; qiݨS{>}UZ8oZy[lX!t : Xu6ue! -\ '׎KV>19>9ɟn(%hlgPK S` C gpN3d:e:Kt8,[xȲJֽd/._,=TL -جp,Y>k a'I]Fa3Jx^* @V.۝2m.vh۪THsFFNT%Sn"fy/ޔg:yض)s.Ԣ dӨ /,EXMnڴh)>&>NvpY ͅ}DէoaG@)K'!{Q#2R"#my?lAA7'\r ]v&g :f&Ji@.fu-(Xoăz`#,zVlÊ mYŪ9:CSGTmFNY$͆1{Hw|Yvw['HRPY`V^ex{7rFcW&;x :%'%ÎW27B3TڦW/zN'O^AENRpוJWҥMHL$.Sx -Y# &֓Wo"t& O->d%eǘԧ9t瀜ssfS*(+쁊^7mtlVW)%U47'nE{ -^2. cjYi26*|_v?%Z?oU~VD?"m< m.V˰ ϓpܜ}IIOIHdCL?[TdWZO%Y?MxO'trJ1󐜘 D:jhQL܅ss"̌̐,*Y1>^f,vrx(գ?(/ %AqaHԓL_}޹3$ں/mL˴NR2ҿZ"6J[aF2p}8gQO:7lzpztkѶl[J-eu m-ˎ-jV'}{6R%s"4F rpt͊'y5m=ի -b eҷ$MT8K'v1vH{걜)>!=9=MFa#R(OL-P[Kj.u~I2 ,4</6[a+ -n(1t% J\l"`SYilHX%c`QwY -nlqő4){R=NGǻz ۪w*TcyޒnGOdT[{HCE;eؒy&c1;ga ГM%@nj5[-Yƭ -C}Yχp^y22F׭n~DE -o+<$L%MEYpvPV.rmH4k0ZT/<hŴu]`Ţ2] *swib nxJR m!Ӧ߱e)U{ ^/i,CHHaMvCf/\؞}Εhϯ0uHixtUoK& 2ԖAi-ԓפ}|o.ݙm m/y1}^cG4Ѩ@z旙2X2Tn) G,%_HP"XB?~UcY YۋXCE}FݙB3=f^¨G -a'kyk&zίGM-חFl:V@vBDlud2lHL OI$5̇KYh(bmy S_uQ?-#Xቊ&3Gß :3aܲ--ˬz."3O WY3hrWe:TʆmDm[ Z K.y'6?wK0Fev4g.'.Q f`>t| "uz\kc2Sƫf+jal__[dhUhѪ -j'9@k[2V"8ƪ~hF寉=6s(^ou9p= mՑ{FiD1U - @u]³µ][<]|vl]mP(}- .J[<ͅH `8՟ekIelgc?FlYAr\`8L#t`JzK i1V:-b己oHQnX5> HG鬐1s%zjFELCL:L,&>Bs`/23cf}*>]TgC&IO ``.Cz, 6|mMmyR` Z; \A39!e=. gOmfbǙLq+&oLH1'\nɊiv@={Q}֚Nje)0~% *V $$HN;+~'\$DfSk7S^\Ǵ,X`c8DtI:Q€%l"‘2DD 'bk:~&*Js=emft(n*;H9< -DKkkxNGKر.w2UA_NH}춓okE:N}?\ci&oÁĜX&1aF`]rtJ QII͈'lTsx|%-0>Rs.)=Cvϼr/5(?=f"NM)WRέ Q Zz;/Q ƗZR1!(`#J`7Rp"4+P֙et^)OL -12 ƻ,Y0uN3y˜]pU ]Ys=1N_ouT8LV eMd] -1mYgnk},hqi@ɪ辀6,+#MIK bK#v -UJ?bc6R`19-AL5bвJ>nJ7Ӧd[?ddG},rDR'EǻnUZFڈ<}X|;b/z O}@/Kj|\ X2: (N'.i;!̟lJ3uGS_M*&~̇A,a#7KY3?0ģ霘Mb~]*.">5u=l ܟeuV;3#09)ƧLSٔ^uvnuqq m -ߢ(.WrZL:$F(m`QC -n' ,IQQQ2}&1?Pr+P\t\heI9\aW YD4aYL$i vW2qjf ̰58NWץ HUW{^i*֧hS_|b"$ckZUe` NݎхNtB贝.i" -01;%PO@.2b0gcc@؋|ZA7ŔXᓕg*,([pthUUh(FCLj+|2$#C6l#=n"SecM$cA;QS_\u ddy@R8F:qŹD+T/Ӈb|Q,]_S];f<;ISn/Y]SGhV1.z2)4(lD-[4eݵjjejl'Zް=A9ZJ;&dJ%C~+#P-1UQ'8Fy3:`{P (\IʁA,?' s^A+RlLRSQs4B_fiW=lPó;[kn/b)Ϥ`cι*gN^I)Üyޤ4Wqh 1gKvw >y4bԩ{2m )rSҥdh-ІYořY^MyrܵOVv󪄟~mу~&W*;-N)طg[%Hj )E[H奼MG×2Ʀw-s0V[4`/ڒisƍcG24t keĐkMzMJ{aK>#=m=l$9[WJk!*6fVVkBw>'#O;Ҏ|Nrel:Mj^=*;OHFFud<N~~U0#_AG 2x>jT]6ovo#z=~/L;u4v6at>u X\-~JB0(G y5LRR/`5M4uTtܵ@rtt ٿ}^iNb.ߖL-TU9kNd#Ad8FX$pR5,QQ3-)OS rWn^/2*6̡=y61jG<)Ȱ%>,5>( cA0%( ֫?6?@ESƟ{W-=խ?09z\x|OM6A:ro<h)? "0(jl7q8 3v~2Oci#ѕ7|{ -{@i=NPןv|dW\j)cq.6?>2XqMd*^[7QakwڙLAvx7:̄o7*J^!p+Yq(b3"VjGfei'In>mSFnb:%.:}Q:_8sϤ^b{3C>ԵdKJfJcܰ!RY.rAҳ[$?fga圭bQa҈smd^{[xNGT\9N[]nUE&.E"ո ))TYaIT3צǤD!&^OM!Q!5FVL<~^[ f;xy/' xp◿gRro1uz ڹl694.k⿃#-@Ιl~aޥI,Wn*J -ߩzN;+jBs3tI:9 9H *;8'9Kxف"$&k e<h -Vf\r@;FL}F \&8\,bG@l#oRS[ h[K;*؁?`gs^r__Uǟcځ$1؋kM'h{]͂YY j+|\xؕJɬ \[/ckܓO/|C c sguxZR-bGߩz%`7;Nd~.(nd01 HӜ?սv~&GMdW5$W_&v%c^y~K/ٌF{'boͮ$T_LH{Dڛb%vy cSt_],(']$ڕC3([2/l%tfo[%魵7n魙;k zk̽05lRRh_ixݨR4 /lz\7؏3=og37I^i|]!lk IhӲ1yVX9=yLq)YdS j ,OLZfcĶ)ק6o^#y iik716lZ -endstream endobj 115 0 obj <> endobj 116 0 obj <> endobj 117 0 obj <>stream -HLMk@g]CEK{؛ K+nq}K2ɋml3y1tVzQ<]o;<0l!nwͳ'NO:zut`^>^騥NG/_ ¨&į2s5hX8?&W"Z*O@=V'E{>yU涪2P,3-![S:K3zHٜ.NsWI*<ѐjč17֗ n@ɎTCף]f%QVU|#YYW\Wh: GSFTs fz"RiLH$_`ɍώD_PP&)+*Up `HjirZj׎ҹ\ -0 -endstream endobj 101 0 obj <>stream -x+| -endstream endobj 102 0 obj <>stream -HT[oL_N_F4iUE}lӊ߱w Iaٳs9svhxYE4b衉>pFE&2XXa\ 9ieF2m8CS47 -eM"xwl2"(ͺ]<>uE\ $!+X6 -tCS4~aoI}y3Q1 0!tEJIum1W2c_]m}ጇ[k pR9㣭"p 4`}.k__O%|K("UWm!}s^_XhX**>k=n4yka-\[@l);jZJ#j9jb]ܺfax/.pLS=U9/JW_+jD×&-\(!p>stream -HtTˎ0WtI*l^n[uTu"uQf&$6<؇OO~Ry!E(ĨnLfgQ6>;TlHaw~(J1CkR͎P*Y-E:Þx0@Q\{=,ŧl\F~1U<,sZ֠lB l<śHrkT$ o_yKu[IhPQHA"]2`vޱ۞aԖFHNz%MuIri$!7+ce -AjBE0Y,:`Sn.ȗ6 hvA3"<-,v=XR}; !y @i -~M໊^TǢNH5 FӓTqK!ž傪/h`Tݖwj*y34J 5vbo$"ۋ(=yY jβsV_g"&g-7~PvgopaslƜ)1? -B3 H~X _?2EqT]B5>stream -HTMs0WpĝuLГlQ$WI#]I6Koh޾ vFNӠ]l_/I\L$/؄—ǯ7˨p#b΂Ԍ7Rw82-E*R۽،\ gYBhU^ Fe!*c¨M o[9o":nԴFA(C &8Rplct[}2,ȉIeJIU A3NE h̗;KwJ{6hԣk.50 -o*,; dil_sgډ} *T9bbΠc%~X32.;7Irocge@ץygFNƼ*oC{2k\ɩv%g7K@Ylr{KQ_bQY*&g;;\sGxnlv >nE9ࣃߪ?m]1]H졡yE쪛'!2 a[-'N -endstream endobj 105 0 obj <>stream -HTM0ȑT`8VU?m`-$Ϳ!V=ٚy3OOOJF-mW#EQ$ tT RENH6x`*#ѥ2\N:޸81eqQ/Oߖݬ4S3㌍y%w'Pv'T3AcwAI~(4h:[ԹԌ{NNΈ [Ȃ#wa)(Z‡M0ȇVN™tGq[ #Iى1Y4bԣmCUg^eAKY- L(Z{3 wQsٸۡ{ٸ'ڻqn @#;uCxBKcaѡZ !pzsD'b]d0Fx(;4~i Qv\yjܰѬp_`6>ig( >stream -HUv +D=1G Y}7Ͷ5Y *rP$~ptze_7e^ٱ|䔟S\u]eM -]*-z gH( R+"B -f{4ڍ -S%%T U0KX G+^ wU[z{P{( ZkqcEONiJ ϵ1bHI3SgU]N*i>stream -HTn0Wp4UqXʡ(a6@iQcX9x͛W՛CF<;24)gkL:$iKN椹4?QocTR4ay%f'mcΎڞil\4C1q6N#rW;R?Fvzkxص RqK!Cc@T2$gCy@ڴR(+SJv@HS gcU(toP{21>~G-w[;bn6]"/}iu|Ea9YbjQJkD’꽟|aLkFb\j/(/`:|̬Ktu -v$msP" ;ۺPͭHpOM}dd/1<B.AI|`^Le@׼\1$ WJz™d˵~#MAމD<<BKU $r ios'l -endstream endobj 108 0 obj <>stream -HTr0+T Q~0mʦB8n"i' `4q]2 E{9W7FSIZ_.02 ge~}u9{7gF[kMB] -"(P%4EJfw#j<9'Z,4f*s -=)9Cr)5!2F4zp0r(6^VUhyDXvm*G2g-Ze5*]1\fuBq?JKTvX˹΃cXBBi>@8\ .)> X 02i=4  8$s4G,]>mg Ɍ))h&; -Xdu̯EM+W۪m}\_btm >-7&ubɰԶɰ3Wλ~99O9dJ18~\Tb< [ uc@H6BΫ{Wufq|.ut:e[^Wƒk<` `,L0:sjoxx3ܣ/cK(a?{Wqzp0_pU&uЇ"KP꨿=soh狞_l\_YzjsNWwM[CUMŜIxR?\"FK): "(džc qb? ZFd -endstream endobj 109 0 obj <>stream -HdN0t%2xwb(B-{SqaPLMʃLRTQbe&~ Qދ: -MxUW(1z -*t9ǡu%k]* z|]_f A!&nՄX}բr5x)ee`h1":2 {)(Zߖer0}Dv{g3<8:NLPm拯ď %Y1ySvH'v>Ӡ8veD E -:R:ȸ&,;R]S:4󧷋u~BF>ͼJ*<X᫄hP}b6΋;PƶLۚe'pD,ǔ7' 9-N+'1gc -,%_0 uQ{DVuݒiR)KJ&얁%# e8/29 6P}\m{[M -endstream endobj 110 0 obj <>stream -x ; -0bK-͇`+ZXI` O (XWfhPO%T}V a;(؎nZQz%kx QJtoo'fC -endstream endobj 95 0 obj <> endobj 96 0 obj <>stream -hޜwTTϽwz0z.0. Qf Ml@DEHb!(`HPb0dFJ|yyǽgs{.$O./ 'z8WGбx0Y驾A@$/7z HeOOҬT_lN:K"N3"$F/JPrb[䥟}Qd[Sl1x{#bG\NoX3I[ql2$ 8xtrp/8 pCfq.Knjm͠{r28?.)ɩL^6g,qm"[Z[Z~Q7%" -3R`̊j[~: w!$E}kyhyRm333: }=#vʉe -tqX)I)B>== <8Xȉ9yP:8p΍Lg kk Ѐ$t!0V87`ɀ2A. -@JPA#h'@8 .: ``a!2D!UH 2 dA>P ECqB**Z:]B=h~L2  5pN:|ó@ QC !H,G6 H9R ]H/r Aw( Q(OTJCm@*QGQ-(j MF+ 6h/*t:].G7Зw7 Xa<1:L1s3bXyeb~19 vGĩp+5qy^ oó|= ?'Htv`Ba3BDxHxE$Չ"XAP44077&9$An0;T2421t.54ld+s;# V]=iY9FgM֚k&=%Ō:nc1gcbcfX.}lGv{c)LŖN퉛w/p+/<j$.$%&㒣OdxTԂԑ4i3|o~C:&S@L u[Uo3C3OfIgwdO|;W-wsz 17jl8c͉̈́3+{%lKWr[ $ -llGmnacOkE&EEY׾2⫅;K,KhtiN=e²{^-_V^Oo§s]?TWީrjVQ=w}`嚢zԶiו8>k׍ - E  [ly邟~_Y53rW򯎼^{7so}x>|쇊z>yz -endstream endobj 51 0 obj <> endobj 59 0 obj <> endobj 60 0 obj <> endobj 58 0 obj <> endobj 61 0 obj <> endobj 57 0 obj <> endobj 62 0 obj <> endobj 56 0 obj <> endobj 63 0 obj <> endobj 55 0 obj <> endobj 64 0 obj <> endobj 54 0 obj <> endobj 73 0 obj <> endobj 66 0 obj <> endobj 65 0 obj <> endobj 53 0 obj <> endobj 74 0 obj <> endobj 52 0 obj <> endobj 75 0 obj <> endobj 72 0 obj <> endobj 68 0 obj <> endobj 71 0 obj <> endobj 69 0 obj <> endobj 70 0 obj <> endobj 67 0 obj <> endobj 76 0 obj <> endobj 80 0 obj <> endobj xref -0 134 -0000000000 65535 f -0000033772 00000 n -0000215821 00000 n -0000222881 00000 n -0000033912 00000 n -0000104611 00000 n -0000215764 00000 n -0000214255 00000 n -0000215245 00000 n -0000215431 00000 n -0000147442 00000 n -0000110007 00000 n -0000034255 00000 n -0000098319 00000 n -0000034398 00000 n -0000091339 00000 n -0000097612 00000 n -0000034541 00000 n -0000084250 00000 n -0000034684 00000 n -0000075349 00000 n -0000081685 00000 n -0000034827 00000 n -0000069577 00000 n -0000034970 00000 n -0000064052 00000 n -0000035113 00000 n -0000035256 00000 n -0000039441 00000 n -0000009643 00000 n -0000009786 00000 n -0000013259 00000 n -0000042043 00000 n -0000046374 00000 n -0000046589 00000 n -0000046756 00000 n -0000016082 00000 n -0000017247 00000 n -0000017314 00000 n -0000017482 00000 n -0000090622 00000 n -0000043206 00000 n -0000044375 00000 n -0000044462 00000 n -0000044635 00000 n -0000082388 00000 n -0000083809 00000 n -0000083864 00000 n -0000084032 00000 n -0000068950 00000 n -0000009300 00000 n -0000247104 00000 n -0000248762 00000 n -0000248582 00000 n -0000248081 00000 n -0000247917 00000 n -0000247688 00000 n -0000247494 00000 n -0000247339 00000 n -0000247197 00000 n -0000247292 00000 n -0000247447 00000 n -0000247641 00000 n -0000247870 00000 n -0000248034 00000 n -0000248418 00000 n -0000248293 00000 n -0000249407 00000 n -0000248966 00000 n -0000249202 00000 n -0000249360 00000 n -0000249155 00000 n -0000248919 00000 n -0000248247 00000 n -0000248716 00000 n -0000248871 00000 n -0000249453 00000 n -0000009385 00000 n -0000009446 00000 n -0000009576 00000 n -0000249501 00000 n -0000223558 00000 n -0000223046 00000 n -0000109349 00000 n -0000104117 00000 n -0000097779 00000 n -0000090798 00000 n -0000081872 00000 n -0000074876 00000 n -0000069116 00000 n -0000039617 00000 n -0000013393 00000 n -0000013884 00000 n -0000013932 00000 n -0000009360 00000 n -0000244268 00000 n -0000244413 00000 n -0000000125 00000 n -0000000000 00000 f -0000000016 00000 n -0000033409 00000 n -0000238114 00000 n -0000238194 00000 n -0000239109 00000 n -0000239836 00000 n -0000240628 00000 n -0000241313 00000 n -0000241993 00000 n -0000242719 00000 n -0000243521 00000 n -0000244111 00000 n -0000225202 00000 n -0000225557 00000 n -0000225730 00000 n -0000224031 00000 n -0000237394 00000 n -0000237449 00000 n -0000237623 00000 n -0000015089 00000 n -0000018028 00000 n -0000018195 00000 n -0000040120 00000 n -0000056082 00000 n -0000056293 00000 n -0000040946 00000 n -0000048853 00000 n -0000049042 00000 n -0000014338 00000 n -0000019632 00000 n -0000019827 00000 n -0000030679 00000 n -0000030716 00000 n -0000030577 00000 n -0000000000 00000 f -trailer -<<32C9EE41F24FB64B9FDB1A56BC0CD8E7>]>> -startxref -249692 -%%EOF diff --git a/assets/eip-5988/reference-implementation/.gitkeep b/assets/eip-5988/reference-implementation/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/eip-5988/test/poseidon/test_vectors.txt b/assets/eip-5988/test/poseidon/test_vectors.txt deleted file mode 100644 index ad3c348..0000000 --- a/assets/eip-5988/test/poseidon/test_vectors.txt +++ /dev/null @@ -1,33 +0,0 @@ -# poseidonperm_x5_255_3 -Input: -['0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000001', '0x0000000000000000000000000000000000000000000000000000000000000002'] -Output: -['0x28ce19420fc246a05553ad1e8c98f5c9d67166be2c18e9e4cb4b4e317dd2a78a', '0x51f3e312c95343a896cfd8945ea82ba956c1118ce9b9859b6ea56637b4b1ddc4', '0x3b2b69139b235626a0bfb56c9527ae66a7bf486ad8c11c14d1da0c69bbe0f79a'] - -# poseidonperm_x5_255_5 -Input: -['0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000001', '0x0000000000000000000000000000000000000000000000000000000000000002', '0x0000000000000000000000000000000000000000000000000000000000000003', '0x0000000000000000000000000000000000000000000000000000000000000004'] -Output: -['0x2a918b9c9f9bd7bb509331c81e297b5707f6fc7393dcee1b13901a0b22202e18', '0x65ebf8671739eeb11fb217f2d5c5bf4a0c3f210e3f3cd3b08b5db75675d797f7', '0x2cc176fc26bc70737a696a9dfd1b636ce360ee76926d182390cdb7459cf585ce', '0x4dc4e29d283afd2a491fe6aef122b9a968e74eff05341f3cc23fda1781dcb566', '0x03ff622da276830b9451b88b85e6184fd6ae15c8ab3ee25a5667be8592cce3b1'] - -# poseidonperm_x5_254_3 -Input: -['0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000001', '0x0000000000000000000000000000000000000000000000000000000000000002'] -Output: -['0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a', '0x0fca49b798923ab0239de1c9e7a4a9a2210312b6a2f616d18b5a87f9b628ae29', '0x0e7ae82e40091e63cbd4f16a6d16310b3729d4b6e138fcf54110e2867045a30c'] - -# poseidonperm_x5_254_5 -Input: -['0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000001', '0x0000000000000000000000000000000000000000000000000000000000000002', '0x0000000000000000000000000000000000000000000000000000000000000003', '0x0000000000000000000000000000000000000000000000000000000000000004'] -Output: -['0x299c867db6c1fdd79dcefa40e4510b9837e60ebb1ce0663dbaa525df65250465', '0x1148aaef609aa338b27dafd89bb98862d8bb2b429aceac47d86206154ffe053d', '0x24febb87fed7462e23f6665ff9a0111f4044c38ee1672c1ac6b0637d34f24907', '0x0eb08f6d809668a981c186beaf6110060707059576406b248e5d9cf6e78b3d3e', '0x07748bc6877c9b82c8b98666ee9d0626ec7f5be4205f79ee8528ef1c4a376fc7'] - -# starkadperm_x5_256_3 -Input: -['0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000001', '0x0000000000000000000000000000000000000000000000000000000000000002'] -Output: -['0xd4a965f154ff8634896b30da71a3b5dfdbf8f87bba6dd053c7dc32b289464a50', '0x21edf7423857b5fd375ac0808409b50e7b92c580fe0b8415bb0a8cf352995118', '0x73b5e0fb2390fd139c9fa8a3e3a2c2349e504b02f80eef19e434070fa4ce147d'] -Input (concat): -0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002 -Output (concat): -0xd4a965f154ff8634896b30da71a3b5dfdbf8f87bba6dd053c7dc32b289464a5021edf7423857b5fd375ac0808409b50e7b92c580fe0b8415bb0a8cf35299511873b5e0fb2390fd139c9fa8a3e3a2c2349e504b02f80eef19e434070fa4ce147d \ No newline at end of file diff --git a/assets/eip-6059/contracts/IERC6059.sol b/assets/eip-6059/contracts/IERC6059.sol deleted file mode 100644 index c24ae4e..0000000 --- a/assets/eip-6059/contracts/IERC6059.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - - -interface IERC6059 { - struct DirectOwner { - uint256 tokenId; - address ownerAddress; - bool isNft; - } - - event NestTransfer( - address indexed from, - address indexed to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 indexed tokenId - ); - - event ChildProposed( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event ChildAccepted( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event AllChildrenRejected(uint256 indexed tokenId); - - event ChildTransferred( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId, - bool fromPending - ); - - struct Child { - uint256 tokenId; - address contractAddress; - } - - function ownerOf(uint256 tokenId) external view returns (address owner); - - function directOwnerOf( - uint256 tokenId - ) external view returns (address, uint256, bool); - - function burn( - uint256 tokenId, - uint256 maxRecursiveBurns - ) external returns (uint256); - - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) external; - - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) external; - - function rejectAllChildren(uint256 parentId, uint256 maxRejections) - external; - - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) external; - - function childrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function pendingChildrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function childOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function pendingChildOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) external; -} diff --git a/assets/eip-6059/contracts/NestableToken.sol b/assets/eip-6059/contracts/NestableToken.sol deleted file mode 100644 index 208094e..0000000 --- a/assets/eip-6059/contracts/NestableToken.sol +++ /dev/null @@ -1,1468 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//Generally all interactions should propagate downstream - -pragma solidity ^0.8.16; - -import "./IERC6059.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -error ChildAlreadyExists(); -error ChildIndexOutOfRange(); -error ERC721AddressZeroIsNotaValidOwner(); -error ERC721ApprovalToCurrentOwner(); -error ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); -error ERC721ApproveToCaller(); -error ERC721InvalidTokenId(); -error ERC721MintToTheZeroAddress(); -error ERC721NotApprovedOrOwner(); -error ERC721TokenAlreadyMinted(); -error ERC721TransferFromIncorrectOwner(); -error ERC721TransferToNonReceiverImplementer(); -error ERC721TransferToTheZeroAddress(); -error IdZeroForbidden(); -error IsNotContract(); -error MaxPendingChildrenReached(); -error MaxRecursiveBurnsReached(address childContract, uint256 childId); -error MintToNonNestableImplementer(); -error NestableTooDeep(); -error NestableTransferToDescendant(); -error NestableTransferToNonNestableImplementer(); -error NestableTransferToSelf(); -error NotApprovedOrDirectOwner(); -error PendingChildIndexOutOfRange(); -error UnexpectedChildId(); -error UnexpectedNumberOfChildren(); - -/** - * @title NestableToken - * @author RMRK team - * @notice Smart contract of the Nestable module. - * @dev This contract is hierarchy agnostic and can support an arbitrary number of nested levels up and down, as long as - * gas limits allow it. - */ -contract NestableToken is Context, IERC165, IERC721, IERC6059 { - using Address for address; - - uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100; - - // Mapping owner address to token count - mapping(address => uint256) private _balances; - - // Mapping from token ID to approver address to approved address - // The approver is necessary so approvals are invalidated for nested children on transfer - // WARNING: If a child NFT returns to a previous root owner, old permissions would be active again - mapping(uint256 => mapping(address => address)) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // ------------------- NESTABLE -------------- - - // Mapping from token ID to DirectOwner struct - mapping(uint256 => DirectOwner) private _directOwners; - - // Mapping of tokenId to array of active children structs - mapping(uint256 => Child[]) private _activeChildren; - - // Mapping of tokenId to array of pending children structs - mapping(uint256 => Child[]) private _pendingChildren; - - // Mapping of child token address to child token ID to whether they are pending or active on any token - // We might have a first extra mapping from token ID, but since the same child cannot be nested into multiple tokens - // we can strip it for size/gas savings. - mapping(address => mapping(uint256 => uint256)) private _childIsInActive; - - // -------------------------- MODIFIERS ---------------------------- - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @dev If the caller is not the owner of the token or approved to manage it by its owner, the execution will be - * reverted. - * @param tokenId ID of the token to check - */ - function _onlyApprovedOrOwner(uint256 tokenId) private view { - if (!_isApprovedOrOwner(_msgSender(), tokenId)) - revert ERC721NotApprovedOrOwner(); - } - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrOwner(uint256 tokenId) { - _onlyApprovedOrOwner(tokenId); - _; - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or it its direct owner. - * @dev This does not delegate to ownerOf, which returns the root owner, but rater uses an owner from DirectOwner - * struct. - * @dev The execution is reverted if the caller is not immediate owner or approved to manage the given token. - * @dev Used for parent-scoped transfers. - * @param tokenId ID of the token to check. - */ - function _onlyApprovedOrDirectOwner(uint256 tokenId) private view { - if (!_isApprovedOrDirectOwner(_msgSender(), tokenId)) - revert NotApprovedOrDirectOwner(); - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or is its direct owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrDirectOwner(uint256 tokenId) { - _onlyApprovedOrDirectOwner(tokenId); - _; - } - - // ------------------------------- ERC721 --------------------------------- - /** - * @inheritdoc IERC165 - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC165).interfaceId || - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC721Metadata).interfaceId || - interfaceId == type(IERC6059).interfaceId; - } - - /** - * @inheritdoc IERC721 - */ - function balanceOf(address owner) public view virtual returns (uint256) { - if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner(); - return _balances[owner]; - } - - //////////////////////////////////////// - // TRANSFERS - //////////////////////////////////////// - - /** - * @inheritdoc IERC721 - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _transfer(from, to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual { - safeTransferFrom(from, to, tokenId, ""); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _safeTransfer(from, to, tokenId, data); - } - - /** - * @notice Used to transfer the token into another token. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @param from Address of the direct owner of the token to be transferred - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token being transferred - * @param destinationId ID of the token to receive the token being transferred - */ - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _nestTransfer(from, to, tokenId, destinationId, data); - } - - /** - * @notice Used to safely transfer the token form `from` to `to`. - * @dev The function checks that contract recipients are aware of the ERC721 protocol to prevent tokens from being - * forever locked. - * @dev This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. implement alternative - * mechanisms to perform token transfer, such as signature-based. - * @dev Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - * @param data Additional data with no specified format, sent in call to `to` - */ - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _transfer(from, to, tokenId); - if (!_checkOnERC721Received(from, to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to transfer the token from `from` to `to`. - * @dev As opposed to {transferFrom}, this imposes no restrictions on msg.sender. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - */ - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, 0, to, false); - _balances[to] += 1; - - emit Transfer(from, to, tokenId); - emit NestTransfer(immediateOwner, to, parentId, 0, tokenId); - - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - } - - /** - * @notice Used to transfer a token into another token. - * @dev Attempting to nest a token into `0x0` address will result in reverted transaction. - * @dev Attempting to nest a token into itself will result in reverted transaction. - * @param from Address of the account currently owning the given token - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token to transfer - * @param destinationId ID of the token receiving the given token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestTransfer( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - if (to == address(this) && tokenId == destinationId) - revert NestableTransferToSelf(); - - // Destination contract checks: - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert NestableTransferToNonNestableImplementer(); - _checkForInheritanceLoop(tokenId, to, destinationId); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - to, - parentId, - destinationId, - tokenId - ); - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, destinationId, to, true); - _balances[to] += 1; - - // Sending to NFT: - _sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data); - } - - /** - * @notice Used to send a token to another token. - * @dev If the token being sent is currently owned by an externally owned account, the `parentId` should equal `0`. - * @dev Emits {Transfer} event. - * @dev Emits {NestTransfer} event. - * @param from Address from which the token is being sent - * @param to Address of the collection smart contract of the token to receive the given token - * @param parentId ID of the current parent token of the token being sent - * @param destinationId ID of the tokento receive the token being sent - * @param tokenId ID of the token being sent - * @param data Additional data with no specified format, sent in the addChild call - */ - function _sendToNFT( - address from, - address to, - uint256 parentId, - uint256 destinationId, - uint256 tokenId, - bytes memory data - ) private { - IERC6059 destContract = IERC6059(to); - destContract.addChild(destinationId, tokenId, data); - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(from, to, parentId, destinationId, tokenId); - - emit Transfer(from, to, tokenId); - emit NestTransfer(from, to, parentId, destinationId, tokenId); - } - - /** - * @notice Used to check if nesting a given token into a specified token would create an inheritance loop. - * @dev If a loop would occur, the tokens would be unmanageable, so the execution is reverted if one is detected. - * @dev The check for inheritance loop is bounded to guard against too much gas being consumed. - * @param currentId ID of the token that would be nested - * @param targetContract Address of the collection smart contract of the token into which the given token would be - * nested - * @param targetId ID of the token into which the given token would be nested - */ - function _checkForInheritanceLoop( - uint256 currentId, - address targetContract, - uint256 targetId - ) private view { - for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) { - ( - address nextOwner, - uint256 nextOwnerTokenId, - bool isNft - ) = IERC6059(targetContract).directOwnerOf(targetId); - // If there's a final address, we're good. There's no loop. - if (!isNft) { - return; - } - // Ff the current nft is an ancestor at some point, there is an inheritance loop - if (nextOwner == address(this) && nextOwnerTokenId == currentId) { - revert NestableTransferToDescendant(); - } - // We reuse the parameters to save some contract size - targetContract = nextOwner; - targetId = nextOwnerTokenId; - unchecked { - ++i; - } - } - revert NestableTooDeep(); - } - - //////////////////////////////////////// - // MINTING - //////////////////////////////////////// - - /** - * @notice Used to safely mint a token to a specified address. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param to Address to which to safely mint the gven token - * @param tokenId ID of the token to mint to the specified address - */ - function _safeMint(address to, uint256 tokenId) internal virtual { - _safeMint(to, tokenId, ""); - } - - /** - * @notice Used to safely mint the token to the specified address while passing the additional data to contract - * recipients. - * @param to Address to which to mint the token - * @param tokenId ID of the token to mint - * @param data Additional data to send with the tokens - */ - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _mint(to, tokenId); - if (!_checkOnERC721Received(address(0), to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to mint a specified token to a given address. - * @dev WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - `to` cannot be the zero address. - * @dev Emits a {Transfer} event. - * @param to Address to mint the token to - * @param tokenId ID of the token to mint - */ - function _mint(address to, uint256 tokenId) internal virtual { - _innerMint(to, tokenId, 0); - - emit Transfer(address(0), to, tokenId); - emit NestTransfer(address(0), to, 0, 0, tokenId); - - _afterTokenTransfer(address(0), to, tokenId); - _afterNestedTokenTransfer(address(0), to, 0, 0, tokenId); - } - - /** - * @notice Used to mint a child token to a given parent token. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new child token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestMint( - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert MintToNonNestableImplementer(); - - _innerMint(to, tokenId, destinationId); - _sendToNFT(address(0), to, 0, destinationId, tokenId, data); - } - - /** - * @notice Used to mint a child token into a given parent token. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` must not exist. - * - `tokenId` must not be `0`. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new token - */ - function _innerMint( - address to, - uint256 tokenId, - uint256 destinationId - ) private { - if (to == address(0)) revert ERC721MintToTheZeroAddress(); - if (_exists(tokenId)) revert ERC721TokenAlreadyMinted(); - if (tokenId == 0) revert IdZeroForbidden(); - - _beforeTokenTransfer(address(0), to, tokenId); - _beforeNestedTokenTransfer(address(0), to, 0, destinationId, tokenId); - - _balances[to] += 1; - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId, - isNft: destinationId != 0 - }); - } - - //////////////////////////////////////// - // Ownership - //////////////////////////////////////// - - /** - * @notice Used to retrieve the root owner of the given token. - * @dev Root owner is always the externally owned account. - * @dev If the given token is owned by another token, it will recursively query the parent tokens until reaching the - * root owner. - * @param tokenId ID of the token for which the root owner is being retrieved - * @return address Address of the root owner of the given token - */ - function ownerOf( - uint256 tokenId - ) public view virtual override(IERC6059, IERC721) returns (address) { - (address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf( - tokenId - ); - if (isNft) { - owner = IERC6059(owner).ownerOf(ownerTokenId); - } - return owner; - } - - /** - * @notice Used to retrieve the immediate owner of the given token. - * @dev In the event the NFT is owned by an externally owned account, `tokenId` will be `0` and `isNft` will be - * `false`. - * @param tokenId ID of the token for which the immediate owner is being retrieved - * @return address Address of the immediate owner. If the token is owned by an externally owned account, its address - * will be returned. If the token is owned by another token, the parent token's collection smart contract address - * is returned - * @return uint256 Token ID of the immediate owner. If the immediate owner is an externally owned account, the value - * should be `0` - * @return bool A boolean value signifying whether the immediate owner is a token (`true`) or not (`false`) - */ - function directOwnerOf( - uint256 tokenId - ) public view virtual returns (address, uint256, bool) { - DirectOwner memory owner = _directOwners[tokenId]; - if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId(); - - return (owner.ownerAddress, owner.tokenId, owner.isNft); - } - - //////////////////////////////////////// - // BURNING - //////////////////////////////////////// - - /** - * @notice Used to burn a given token. - * @param tokenId ID of the token to burn - */ - function burn(uint256 tokenId) public virtual { - burn(tokenId, 0); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) public virtual onlyApprovedOrDirectOwner(tokenId) returns (uint256) { - return _burn(tokenId, maxChildrenBurns); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @dev Emits a {NestTransfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function _burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) internal virtual returns (uint256) { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - address owner = ownerOf(tokenId); - _balances[immediateOwner] -= 1; - - _beforeTokenTransfer(owner, address(0), tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - - _approve(address(0), tokenId); - _cleanApprovals(tokenId); - - Child[] memory children = childrenOf(tokenId); - - delete _activeChildren[tokenId]; - delete _pendingChildren[tokenId]; - delete _tokenApprovals[tokenId][owner]; - - uint256 pendingRecursiveBurns; - uint256 totalChildBurns; - - uint256 length = children.length; //gas savings - for (uint256 i; i < length; ) { - if (totalChildBurns >= maxChildrenBurns) - revert MaxRecursiveBurnsReached( - children[i].contractAddress, - children[i].tokenId - ); - delete _childIsInActive[children[i].contractAddress][ - children[i].tokenId - ]; - unchecked { - // At this point we know pendingRecursiveBurns must be at least 1 - pendingRecursiveBurns = maxChildrenBurns - totalChildBurns; - } - // We substract one to the next level to count for the token being burned, then add it again on returns - // This is to allow the behavior of 0 recursive burns meaning only the current token is deleted. - totalChildBurns += - IERC6059(children[i].contractAddress).burn( - children[i].tokenId, - pendingRecursiveBurns - 1 - ) + - 1; - unchecked { - ++i; - } - } - // Can't remove before burning child since child will call back to get root owner - delete _directOwners[tokenId]; - - _afterTokenTransfer(owner, address(0), tokenId); - _afterNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - emit Transfer(owner, address(0), tokenId); - emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId); - - return totalChildBurns; - } - - //////////////////////////////////////// - // APPROVALS - //////////////////////////////////////// - - /** - * @inheritdoc IERC721 - */ - function approve(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - if (to == owner) revert ERC721ApprovalToCurrentOwner(); - - if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) - revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); - - _approve(to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function getApproved( - uint256 tokenId - ) public view virtual returns (address) { - _requireMinted(tokenId); - - return _tokenApprovals[tokenId][ownerOf(tokenId)]; - } - - /** - * @inheritdoc IERC721 - */ - function setApprovalForAll(address operator, bool approved) public virtual { - if (_msgSender() == operator) revert ERC721ApproveToCaller(); - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @inheritdoc IERC721 - */ - function isApprovedForAll( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovals[owner][operator]; - } - - /** - * @notice Used to grant an approval to manage a given token. - * @dev Emits an {Approval} event. - * @param to Address to which the approval is being granted - * @param tokenId ID of the token for which the approval is being granted - */ - function _approve(address to, uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - _tokenApprovals[tokenId][owner] = to; - emit Approval(owner, to, tokenId); - } - - /** - * @notice Used to update the owner of the token and clear the approvals associated with the previous owner. - * @dev The `destinationId` should equal `0` if the new owner is an externally owned account. - * @param tokenId ID of the token being updated - * @param destinationId ID of the token to receive the given token - * @param to Address of account to receive the token - * @param isNft A boolean value signifying whether the new owner is a token (`true`) or externally owned account - * (`false`) - */ - function _updateOwnerAndClearApprovals( - uint256 tokenId, - uint256 destinationId, - address to, - bool isNft - ) internal { - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId, - isNft: isNft - }); - - // Clear approvals from the previous owner - _approve(address(0), tokenId); - _cleanApprovals(tokenId); - } - - /** - * @notice Used to remove approvals for the current owner of the given token. - * @param tokenId ID of the token to clear the approvals for - */ - function _cleanApprovals(uint256 tokenId) internal virtual {} - - //////////////////////////////////////// - // UTILS - //////////////////////////////////////// - - /** - * @notice Used to check whether the given account is allowed to manage the given token. - * @dev Requirements: - * - * - `tokenId` must exist. - * @param spender Address that is being checked for approval - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token - */ - function _isApprovedOrOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to check whether the account is approved to manage the token or its direct owner. - * @param spender Address that is being checked for approval or direct ownership - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token or its - * direct owner - */ - function _isApprovedOrDirectOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - (address owner, uint256 parentId, ) = directOwnerOf(tokenId); - // When the parent is an NFT, only it can do operations - if (parentId != 0) { - return (spender == owner); - } - // Otherwise, the owner or approved address can - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to enforce that the given token has been minted. - * @dev Reverts if the `tokenId` has not been minted yet. - * @dev The validation checks whether the owner of a given token is a `0x0` address and considers it not minted if - * it is. This means that both tokens that haven't been minted yet as well as the ones that have already been - * burned will cause the transaction to be reverted. - * @param tokenId ID of the token to check - */ - function _requireMinted(uint256 tokenId) internal view virtual { - if (!_exists(tokenId)) revert ERC721InvalidTokenId(); - } - - /** - * @notice Used to check whether the given token exists. - * @dev Tokens start existing when they are minted (`_mint`) and stop existing when they are burned (`_burn`). - * @param tokenId ID of the token being checked - * @return bool The boolean value signifying whether the token exists - */ - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _directOwners[tokenId].ownerAddress != address(0); - } - - /** - * @notice Used to invoke {IERC721Receiver-onERC721Received} on a target address. - * @dev The call is not executed if the target address is not a contract. - * @param from Address representing the previous owner of the given token - * @param to Yarget address that will receive the tokens - * @param tokenId ID of the token to be transferred - * @param data Optional data to send along with the call - * @return bool Boolean value signifying whether the call correctly returned the expected magic value - */ - function _checkOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory data - ) private returns (bool) { - if (to.isContract()) { - try - IERC721Receiver(to).onERC721Received( - _msgSender(), - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert ERC721TransferToNonReceiverImplementer(); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - //////////////////////////////////////// - // CHILD MANAGEMENT PUBLIC - //////////////////////////////////////// - - /** - * @notice Used to add a child token to a given parent token. - * @dev This adds the iichild token into the given parent token's pending child tokens array. - * @dev You MUST NOT call this method directly. To add a a child to an NFT you must use either - * `nestTransfer`, `nestMint` or `transferChild` to the NFT. - * @dev Requirements: - * - * - `ownerOf` on the child contract must resolve to the called contract. - * - The pending array of the parent contract must not be full. - * @param parentId ID of the parent token to receive the new child token - * @param childId ID of the new proposed child token - * @param data Additional data with no specified format - */ - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) public virtual { - _requireMinted(parentId); - - address childAddress = _msgSender(); - if (!childAddress.isContract()) revert IsNotContract(); - - Child memory child = Child({ - contractAddress: childAddress, - tokenId: childId - }); - - _beforeAddChild(parentId, childAddress, childId); - - uint256 length = pendingChildrenOf(parentId).length; - - if (length < 128) { - _pendingChildren[parentId].push(child); - } else { - revert MaxPendingChildrenReached(); - } - - // Previous length matches the index for the new child - emit ChildProposed(parentId, length, childAddress, childId); - - _afterAddChild(parentId, childAddress, childId); - } - - /** - * @notice @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) public virtual onlyApprovedOrOwner(parentId) { - _acceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @dev Requirements: - * - * - `tokenId` must exist - * - `index` must be in range of the pending children array - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual { - if (pendingChildrenOf(parentId).length <= childIndex) - revert PendingChildIndexOutOfRange(); - - Child memory child = pendingChildOf(parentId, childIndex); - _checkExpectedChild(child, childAddress, childId); - if (_childIsInActive[childAddress][childId] != 0) - revert ChildAlreadyExists(); - - _beforeAcceptChild(parentId, childIndex, childAddress, childId); - - // Remove from pending: - _removeChildByIndex(_pendingChildren[parentId], childIndex); - - // Add to active: - _activeChildren[parentId].push(child); - _childIsInActive[childAddress][childId] = 1; // We use 1 as true - - emit ChildAccepted(parentId, childIndex, childAddress, childId); - - _afterAcceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @param tokenId ID of the parent token for which to reject all of the pending tokens - */ - function rejectAllChildren(uint256 tokenId, uint256 maxRejections) public virtual onlyApprovedOrOwner(tokenId) { - _rejectAllChildren(tokenId, maxRejections); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @dev Requirements: - * - * - `tokenId` must exist - * @param tokenId ID of the parent token for which to reject all of the pending tokens. - * @param maxRejections Maximum number of expected children to reject, used to prevent from - * rejecting children which arrive just before this operation. - */ - function _rejectAllChildren(uint256 tokenId, uint256 maxRejections) - internal - virtual - { - if (_pendingChildren[tokenId].length > maxRejections) - revert UnexpectedNumberOfChildren(); - - _beforeRejectAllChildren(tokenId); - delete _pendingChildren[tokenId]; - emit AllChildrenRejected(tokenId); - _afterRejectAllChildren(tokenId); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) public virtual onlyApprovedOrOwner(tokenId) { - _transferChild( - tokenId, - to, - destinationId, - childIndex, - childAddress, - childId, - isPending, - data - ); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @dev When transferring a child token, the owner of the token is set to `to`, or is not updated in the event of `to` - * being the `0x0` address. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits {ChildTransferred} event. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function _transferChild( - uint256 tokenId, - address to, - uint256 destinationId, // newParentId - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) internal virtual { - Child memory child; - if (isPending) { - child = pendingChildOf(tokenId, childIndex); - } else { - child = childOf(tokenId, childIndex); - } - _checkExpectedChild(child, childAddress, childId); - - _beforeTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - - if (isPending) { - _removeChildByIndex(_pendingChildren[tokenId], childIndex); - } else { - delete _childIsInActive[childAddress][childId]; - _removeChildByIndex(_activeChildren[tokenId], childIndex); - } - - if (to != address(0)) { - if (destinationId == 0) { - IERC721(childAddress).safeTransferFrom( - address(this), - to, - childId, - data - ); - } else { - // Destination is an NFT - IERC6059(child.contractAddress).nestTransferFrom( - address(this), - to, - child.tokenId, - destinationId, - data - ); - } - } - - emit ChildTransferred( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - _afterTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - } - - function _checkExpectedChild( - Child memory child, - address expectedAddress, - uint256 expectedId - ) private pure { - if ( - expectedAddress != child.contractAddress || - expectedId != child.tokenId - ) revert UnexpectedChildId(); - } - - //////////////////////////////////////// - // CHILD MANAGEMENT GETTERS - //////////////////////////////////////// - - /** - * @notice Used to retrieve the active child tokens of a given parent token. - * @dev Returns array of Child structs existing for parent token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the active child tokens - * @return struct[] An array of Child structs containing the parent token's active child tokens - */ - - function childrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory children = _activeChildren[parentId]; - return children; - } - - /** - * @notice Used to retrieve the pending child tokens of a given parent token. - * @dev Returns array of pending Child structs existing for given parent. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the pending child tokens - * @return struct[] An array of Child structs containing the parent token's pending child tokens - */ - - function pendingChildrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory pendingChildren = _pendingChildren[parentId]; - return pendingChildren; - } - - /** - * @notice Used to retrieve a specific active child token for a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the child is being retrieved - * @param index Index of the child token in the parent token's active child tokens array - * @return struct A Child struct containing data about the specified child - */ - function childOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (childrenOf(parentId).length <= index) revert ChildIndexOutOfRange(); - Child memory child = _activeChildren[parentId][index]; - return child; - } - - /** - * @notice Used to retrieve a specific pending child token from a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the pending child token is being retrieved - * @param index Index of the child token in the parent token's pending child tokens array - * @return struct A Child struct containting data about the specified child - */ - function pendingChildOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (pendingChildrenOf(parentId).length <= index) - revert PendingChildIndexOutOfRange(); - Child memory child = _pendingChildren[parentId][index]; - return child; - } - - /** - * @notice Used to verify that the given child tokwn is included in an active array of a token. - * @param childAddress Address of the given token's collection smart contract - * @param childId ID of the child token being checked - * @return bool A boolean value signifying whether the given child token is included in an active child tokens array - * of a token (`true`) or not (`false`) - */ - function childIsInActive( - address childAddress, - uint256 childId - ) public view virtual returns (bool) { - return _childIsInActive[childAddress][childId] != 0; - } - - // HOOKS - - /** - * @notice Hook that is called before any token transfer. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be transferred to `to`. - * - When `from` is zero, `tokenId` will be minted to `to`. - * - When `to` is zero, ``from``'s `tokenId` will be burned. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after any transfer of tokens. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token has been transferred - * @param to Address to which the token has been transferred - * @param tokenId ID of the token that has been transferred - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param fromTokenId ID of the token from which the given token is being transferred - * @param toTokenId ID of the token to which the given token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token was transferred - * @param to Address to which the token was transferred - * @param fromTokenId ID of the token from which the given token was transferred - * @param toTokenId ID of the token to which the given token was transferred - * @param tokenId ID of the token that was transferred - */ - function _afterNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will receive a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has received a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _afterAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that will accept a pending child token - * @param childIndex Index of the child token to accept in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that has accepted a pending child token - * @param childIndex Index of the child token that was accpeted in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's pending children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's pending children array - */ - function _afterAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will transfer a child token - * @param childIndex Index of the child token that will be transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that is expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that is expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token is being transferred from the pending child - * tokens array (`true`) or from the active child tokens array (`false`) - */ - function _beforeTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called after a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has transferred a child token - * @param childIndex Index of the child token that was transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token was transferred from the pending child tokens - * array (`true`) or from the active child tokens array (`false`) - */ - function _afterTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called before a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will reject all of the pending child tokens - */ - function _beforeRejectAllChildren(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called after a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has rejected all of the pending child tokens - */ - function _afterRejectAllChildren(uint256 tokenId) internal virtual {} - - // HELPERS - - /** - * @notice Used to remove a specified child token form an array using its index within said array. - * @dev The caller must ensure that the length of the array is valid compared to the index passed. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param array An array od Child struct containing info about the child tokens in a given child tokens array - * @param index An index of the child token to remove in the accompanying array - */ - function _removeChildByIndex(Child[] storage array, uint256 index) private { - array[index] = array[array.length - 1]; - array.pop(); - } -} diff --git a/assets/eip-6059/contracts/mocks/ERC721Mock.sol b/assets/eip-6059/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index b31caeb..0000000 --- a/assets/eip-6059/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests with non RMRK implementer - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} -} diff --git a/assets/eip-6059/contracts/mocks/NestableTokenMock.sol b/assets/eip-6059/contracts/mocks/NestableTokenMock.sol deleted file mode 100644 index 2886173..0000000 --- a/assets/eip-6059/contracts/mocks/NestableTokenMock.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../NestableToken.sol"; - -//Minimal public implementation of IRMRKNestable for testing. -contract NestableTokenMock is NestableToken { - constructor() NestableToken() {} - - function mint(address to, uint256 tokenId) external { - _mint(to, tokenId); - } - - function nestMint( - address to, - uint256 tokenId, - uint256 destinationId - ) external { - _nestMint(to, tokenId, destinationId, ""); - } - - // Utility transfers: - - function transfer(address to, uint256 tokenId) public virtual { - transferFrom(_msgSender(), to, tokenId); - } - - function nestTransfer( - address to, - uint256 tokenId, - uint256 destinationId - ) public virtual { - nestTransferFrom(_msgSender(), to, tokenId, destinationId, ""); - } -} diff --git a/assets/eip-6059/hardhat.config.ts b/assets/eip-6059/hardhat.config.ts deleted file mode 100644 index 4289f15..0000000 --- a/assets/eip-6059/hardhat.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HardhatUserConfig } from 'hardhat/config'; -import '@nomicfoundation/hardhat-chai-matchers'; -import '@nomiclabs/hardhat-etherscan'; -import '@typechain/hardhat'; -import 'hardhat-contract-sizer'; -import 'hardhat-gas-reporter'; -import 'solidity-coverage'; - -const config: HardhatUserConfig = { - solidity: { - version: '0.8.16', - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6059/img/eip-6059-abandon-child.png b/assets/eip-6059/img/eip-6059-abandon-child.png deleted file mode 100644 index e97465d..0000000 Binary files a/assets/eip-6059/img/eip-6059-abandon-child.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-nestable-tokens.png b/assets/eip-6059/img/eip-6059-nestable-tokens.png deleted file mode 100644 index cfc15ec..0000000 Binary files a/assets/eip-6059/img/eip-6059-nestable-tokens.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-reject-child.png b/assets/eip-6059/img/eip-6059-reject-child.png deleted file mode 100644 index 2dc8979..0000000 Binary files a/assets/eip-6059/img/eip-6059-reject-child.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-transfer-child-to-eoa.png b/assets/eip-6059/img/eip-6059-transfer-child-to-eoa.png deleted file mode 100644 index e623cbc..0000000 Binary files a/assets/eip-6059/img/eip-6059-transfer-child-to-eoa.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-transfer-child-to-token.png b/assets/eip-6059/img/eip-6059-transfer-child-to-token.png deleted file mode 100644 index ee94447..0000000 Binary files a/assets/eip-6059/img/eip-6059-transfer-child-to-token.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-unnest-child.png b/assets/eip-6059/img/eip-6059-unnest-child.png deleted file mode 100644 index f675c9c..0000000 Binary files a/assets/eip-6059/img/eip-6059-unnest-child.png and /dev/null differ diff --git a/assets/eip-6059/package.json b/assets/eip-6059/package.json deleted file mode 100644 index 4dd7352..0000000 --- a/assets/eip-6059/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "erc-6059", - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/test-helpers": "^0.5.15", - "@primitivefi/hardhat-dodoc": "^0.2.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.3", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "chai": "^4.3.6", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.0", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "hardhat-contract-sizer": "^2.6.1", - "hardhat-gas-reporter": "^1.0.8", - "prettier": "2.7.1", - "prettier-plugin-solidity": "^1.0.0-beta.20", - "solc": "^0.8.9", - "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "walk-sync": "^3.0.0" - } -} diff --git a/assets/eip-6059/test/nestable.ts b/assets/eip-6059/test/nestable.ts deleted file mode 100644 index 9604821..0000000 --- a/assets/eip-6059/test/nestable.ts +++ /dev/null @@ -1,1158 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { BigNumber, constants } from 'ethers'; -import { NestableTokenMock } from '../typechain-types'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -const ADDRESS_ZERO = constants.AddressZero; - -async function parentChildFixture(): Promise<{ - parent: NestableTokenMock; - child: NestableTokenMock; -}> { - const factory = await ethers.getContractFactory('NestableTokenMock'); - - const parent = await factory.deploy(); - await parent.deployed(); - const child = await factory.deploy(); - await child.deployed(); - return { parent, child }; -} - -describe('NestableToken', function () { - let parent: NestableTokenMock; - let child: NestableTokenMock; - let owner: SignerWithAddress; - let tokenOwner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [owner, tokenOwner, ...addrs] = await ethers.getSigners(); - ({ parent, child } = await loadFixture(parentChildFixture)); - }); - - describe('Minting', async function () { - it('cannot mint id 0', async function () { - const tokenId1 = 0; - await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError( - child, - 'IdZeroForbidden', - ); - }); - - it('cannot nest mint id 0', async function () { - const parentId = 1; - await child.mint(owner.address, parentId); - const childId1 = 0; - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'IdZeroForbidden'); - }); - - it('cannot mint already minted token', async function () { - const tokenId1 = 1; - await child.mint(owner.address, tokenId1); - await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError( - child, - 'ERC721TokenAlreadyMinted', - ); - }); - - it('cannot nest mint already minted token', async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted'); - }); - - it('cannot nest mint already minted token', async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted'); - }); - - it('can mint with no destination', async function () { - const tokenId1 = 1; - await child.mint(tokenOwner.address, tokenId1); - expect(await child.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await child.directOwnerOf(tokenId1)).to.eql([tokenOwner.address, bn(0), false]); - }); - - it('has right owners', async function () { - const otherOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(tokenOwner.address, tokenId1); - const tokenId2 = 2; - await parent.mint(otherOwner.address, tokenId2); - const tokenId3 = 3; - await parent.mint(otherOwner.address, tokenId3); - - expect(await parent.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await parent.ownerOf(tokenId2)).to.equal(otherOwner.address); - expect(await parent.ownerOf(tokenId3)).to.equal(otherOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(2); - - await expect(parent.ownerOf(9999)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - }); - - it('cannot mint to zero address', async function () { - await expect(child.mint(ADDRESS_ZERO, 1)).to.be.revertedWithCustomError( - child, - 'ERC721MintToTheZeroAddress', - ); - }); - - it('cannot nest mint to a non-contract destination', async function () { - await expect(child.nestMint(tokenOwner.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'IsNotContract', - ); - }); - - it('cannot nest mint to non nestable receiver', async function () { - const ERC721 = await ethers.getContractFactory('ERC721Mock'); - const nonReceiver = await ERC721.deploy('Non receiver', 'NR'); - await nonReceiver.deployed(); - - await expect(child.nestMint(nonReceiver.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'MintToNonNestableImplementer', - ); - }); - - it('cannot nest mint to a non-existent token', async function () { - await expect(child.nestMint(parent.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - }); - - it('cannot nest mint to zero address', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(child.nestMint(ADDRESS_ZERO, parentId, 1)).to.be.revertedWithCustomError( - child, - 'IsNotContract', - ); - }); - - it('can mint to contract and owners are ok', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // owner is the same adress - expect(await parent.ownerOf(parentId)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - }); - - it('can mint to contract and direct owners are ok', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Direct owner is an address for the parent - expect(await parent.directOwnerOf(parentId)).to.eql([tokenOwner.address, bn(0), false]); - // Direct owner is a contract for the child - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - }); - - it("can mint to contract and parent's children are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - const children = await parent.childrenOf(parentId); - expect(children).to.eql([]); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([[bn(childId1), child.address]]); - expect(await parent.pendingChildOf(parentId, 0)).to.eql([bn(childId1), child.address]); - }); - - it('cannot get child out of index', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.childOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - 'ChildIndexOutOfRange', - ); - }); - - it('cannot get pending child out of index', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.pendingChildOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - 'PendingChildIndexOutOfRange', - ); - }); - - it('can mint multiple children', async function () { - const parentId = 1; - const childId1 = 99; - const childId2 = 100; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId2)).to.equal(tokenOwner.address); - - expect(await child.balanceOf(parent.address)).to.equal(2); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([ - [bn(childId1), child.address], - [bn(childId2), child.address], - ]); - }); - - it('can mint child into child', async function () { - const parentId = 1; - const childId1 = 99; - const granchildId = 999; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - - // Check balances -- yes, technically the counted balance indicates `child` owns an instance of itself - // and this is a little counterintuitive, but the root owner is the EOA. - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - const pendingChildrenOfChunky10 = await parent.pendingChildrenOf(parentId); - const pendingChildrenOfMonkey1 = await child.pendingChildrenOf(childId1); - - expect(pendingChildrenOfChunky10).to.eql([[bn(childId1), child.address]]); - expect(pendingChildrenOfMonkey1).to.eql([[bn(granchildId), child.address]]); - - expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]); - - expect(await child.ownerOf(granchildId)).to.eql(tokenOwner.address); - }); - - it('cannot have too many pending children', async () => { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - - // First 128 should be fine. - for (let i = 1; i <= 128; i++) { - await child.nestMint(parent.address, i, parentId); - } - - await expect(child.nestMint(parent.address, 129, parentId)).to.be.revertedWithCustomError( - child, - 'MaxPendingChildrenReached', - ); - }); - }); - - describe('Interface support', async function () { - it('can support IERC165', async function () { - expect(await parent.supportsInterface('0x01ffc9a7')).to.equal(true); - }); - - it('can support IERC721', async function () { - expect(await parent.supportsInterface('0x80ac58cd')).to.equal(true); - }); - - it('can support IERC6059', async function () { - expect(await parent.supportsInterface('0x42b0e56f')).to.equal(true); - }); - - it('cannot support other interfaceId', async function () { - expect(await parent.supportsInterface('0xffffffff')).to.equal(false); - }); - }); - - describe('Adding child', async function () { - it('cannot add child from user address', async function () { - const tokenOwner1 = addrs[0]; - const tokenOwner2 = addrs[1]; - const parentId = 1; - await parent.mint(tokenOwner1.address, parentId); - const childId1 = 99; - await child.mint(tokenOwner2.address, childId1); - await expect(parent.addChild(parentId, childId1, '0x')).to.be.revertedWithCustomError( - parent, - 'IsNotContract', - ); - }); - }); - - describe('Accept child', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it('can accept child', async function () { - await expect(parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1)) - .to.emit(parent, 'ChildAccepted') - .withArgs(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('can accept child if approved', async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved).acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('can accept child if approved for all', async function () { - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator).acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('cannot accept not owned child', async function () { - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).acceptChild(parentId, 0, child.address, childId1), - ).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot accept child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, otherChildId), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 0, otherAddress, childId1), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('cannot accept children for non existing index', async () => { - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 1, child.address, childId1), - ).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange'); - }); - - async function checkChildWasAccepted() { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - } - }); - - describe('Rejecting children', async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, 99, parentId); - }); - - it('can reject all pending children', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect(parent.connect(tokenOwner).rejectAllChildren(parentId, 3)) - .to.emit(parent, 'AllChildrenRejected') - .withArgs(parentId); - await checkNoChildrenNorPending(parentId); - - // They are still on the child - expect(await child.balanceOf(parent.address)).to.equal(3); - }); - - it('cannot reject all pending children if there are more than expected', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect( - parent.connect(tokenOwner).rejectAllChildren(parentId, 1), - ).to.be.revertedWithCustomError(parent, 'UnexpectedNumberOfChildren'); - }); - - it('can reject all pending children if approved', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const rejecter = addrs[1]; - await parent.connect(tokenOwner).approve(rejecter.address, parentId); - await parent.connect(rejecter).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it('can reject all pending children if approved for all', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it('cannot reject all pending children for not owned pending child', async function () { - const notOwner = addrs[3]; - - await expect( - parent.connect(notOwner).rejectAllChildren(parentId, 2), - ).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner'); - }); - }); - - describe('Burning', async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - }); - - it('can burn token', async function () { - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - await parent.connect(tokenOwner)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can burn token if approved', async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can burn token if approved for all', async function () { - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can recursively burn nested token', async function () { - const childId1 = 99; - const granchildId = 999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, granchildId); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - expect(await child.childrenOf(childId1)).to.eql([[bn(granchildId), child.address]]); - expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]); - - // Sets recursive burns to 2 - await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(0); - expect(await child.balanceOf(parent.address)).to.equal(0); - expect(await child.balanceOf(child.address)).to.equal(0); - - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - await expect(parent.directOwnerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - - await expect(child.ownerOf(childId1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - await expect(child.directOwnerOf(childId1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - - await expect(parent.ownerOf(granchildId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - await expect(parent.directOwnerOf(granchildId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - }); - - it('can recursively burn nested token with the right number of recursive burns', async function () { - // Parent - // -> Child1 - // -> GrandChild1 - // -> GrandChild2 - // -> GreatGrandChild1 - // -> Child2 - // Total tree 5 (4 recursive burns) - const childId1 = 99; - const childId2 = 100; - const grandChild1 = 999; - const grandChild2 = 1000; - const greatGrandChild1 = 9999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - await child.nestMint(child.address, grandChild1, childId1); - await child.nestMint(child.address, grandChild2, childId1); - await child.nestMint(child.address, greatGrandChild1, grandChild2); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId2); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild1); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild2); - await child.connect(tokenOwner).acceptChild(grandChild2, 0, child.address, greatGrandChild1); - - // 0 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 0)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, childId1); - // 1 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 1)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, grandChild1); - // 2 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, grandChild2); - // 3 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 3)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, greatGrandChild1); - // 4 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 4)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, childId2); - // 5 is just enough - await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 5); - }); - - async function checkBurntParent() { - expect(await parent.balanceOf(addrs[1].address)).to.equal(0); - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - } - }); - - describe('Transferring Active Children', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - childId1 = 99; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - }); - - it('can transfer child with to as root owner', async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, false, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(); - }); - - it('can transfer child to another address', async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, false, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it('can transfer child to another NFT', async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - false, - '0x', - ), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot transfer child out of index', async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'ChildIndexOutOfRange'); - }); - - it('cannot transfer child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('can transfer child if approved', async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child if approved for all', async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child with grandchild and children are ok', async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]); - }); - - it('cannot transfer child if not child root owner', async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot transfer child from not existing parent', async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild(badChildId, toOwner, 0, 0, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId'); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe('Transferring Pending Children', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it('can transfer child with to as root owner', async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, true, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(); - }); - - it('can transfer child to another address', async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, true, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it('can transfer child to another NFT', async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - true, - '0x', - ), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot transfer child out of index', async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange'); - }); - - it('cannot transfer child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('can transfer child if approved', async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild(parentId, toOwner, 0, 0,child.address, childId1, true, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child if approved for all', async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child with grandchild and children are ok', async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]); - }); - - it('cannot transfer child if not child root owner', async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot transfer child from not existing parent', async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild(badChildId, toOwner, 0, 0, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId'); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe('Transfer', async function () { - it('can transfer token', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - // Balances and ownership are updated - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - }); - - it('cannot transfer not owned token', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(newOwner).transfer(newOwner.address, tokenId1), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot transfer to address zero', async function () { - const firstOwner = addrs[1]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(firstOwner).transfer(ADDRESS_ZERO, tokenId1), - ).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress'); - }); - - it('can transfer token from approved address (not owner)', async function () { - const firstOwner = addrs[1]; - const approved = addrs[2]; - const newOwner = addrs[3]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - - await parent.connect(firstOwner).approve(approved.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - }); - - it('can transfer not nested token with child to address and owners/children are ok', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await parent.connect(firstOwner).transfer(newOwner.address, parentId); - - // Balances and ownership are updated - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - - expect(await parent.ownerOf(parentId)).to.eql(newOwner.address); - expect(await parent.directOwnerOf(parentId)).to.eql([newOwner.address, bn(0), false]); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(newOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - - // Parent still has its children - expect(await parent.pendingChildrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot directly transfer nested child', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.connect(firstOwner).transfer(newOwner.address, childId1), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('can transfer parent token to token with same owner, family tree is ok', async function () { - const firstOwner = addrs[1]; - const grandParentId = 999; - await parent.mint(firstOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(firstOwner.address)).to.equal(2); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Transfers token parentId to (parent.address, token grandParentId) - await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId); - - // Balances unchanged since root owner is the same - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - - it('can transfer parent token to token with different owner, family tree is ok', async function () { - const firstOwner = addrs[1]; - const otherOwner = addrs[2]; - const grandParentId = 999; - await parent.mint(otherOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // firstOwner calls parent to transfer parent token parent - await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId); - - // Balances update - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - }); - - describe('Nest Transfer', async function () { - let firstOwner: SignerWithAddress; - let parentId: number; - let childId1: number; - - beforeEach(async function () { - firstOwner = addrs[1]; - parentId = 1; - childId1 = 99; - await parent.mint(firstOwner.address, parentId); - await child.mint(firstOwner.address, childId1); - }); - - it('cannot nest tranfer from non immediate owner (owner of parent)', async function () { - const otherParentId = 2; - await parent.mint(firstOwner.address, otherParentId); - // We send it to the parent first - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child.connect(firstOwner).nestTransfer(parent.address, childId1, otherParentId), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot nest tranfer to same NFT', async function () { - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child.connect(firstOwner).nestTransfer(child.address, childId1, childId1), - ).to.be.revertedWithCustomError(child, 'NestableTransferToSelf'); - }); - - it('cannot nest tranfer a descendant same NFT', async function () { - // We can no longer nest transfer it, even if we are the root owner: - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - const grandChildId = 999; - await child.nestMint(child.address, grandChildId, childId1); - // Ownership is now parent->child->granChild - // Cannot send parent to grandChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, grandChildId), - ).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant'); - // Cannot send parent to child - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, childId1), - ).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant'); - }); - - it('cannot nest tranfer if ancestors tree is too deep', async function () { - let lastId = childId1; - for (let i = 101; i <= 200; i++) { - await child.nestMint(child.address, i, lastId); - lastId = i; - } - // Ownership is now parent->child->child->child->child...->lastChild - // Cannot send parent to lastChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, lastId), - ).to.be.revertedWithCustomError(child, 'NestableTooDeep'); - }); - - it('cannot nest tranfer if not owner', async function () { - const notOwner = addrs[3]; - await expect( - child.connect(notOwner).nestTransfer(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot nest tranfer to address 0', async function () { - await expect( - child.connect(firstOwner).nestTransfer(ADDRESS_ZERO, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress'); - }); - - it('cannot nest tranfer to a non contract', async function () { - const newOwner = addrs[2]; - await expect( - child.connect(firstOwner).nestTransfer(newOwner.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'IsNotContract'); - }); - - it('cannot nest tranfer to contract if it does implement IERC6059', async function () { - const ERC721 = await ethers.getContractFactory('ERC721Mock'); - const nonNestable = await ERC721.deploy('Non receiver', 'NR'); - await nonNestable.deployed(); - await expect( - child.connect(firstOwner).nestTransfer(nonNestable.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'NestableTransferToNonNestableImplementer'); - }); - - it('can nest tranfer to IERC6059 contract', async function () { - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - expect(await child.ownerOf(childId1)).to.eql(firstOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - }); - - it('cannot nest tranfer to non existing parent token', async function () { - const notExistingParentId = 9999; - await expect( - child.connect(firstOwner).nestTransfer(parent.address, childId1, notExistingParentId), - ).to.be.revertedWithCustomError(parent, 'ERC721InvalidTokenId'); - }); - }); - - async function checkNoChildrenNorPending(parentId: number): Promise { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([]); - } - - async function checkAcceptedAndPendingChildren( - contract: NestableTokenMock, - tokenId1: number, - expectedAccepted: any[], - expectedPending: any[], - ) { - const accepted = await contract.childrenOf(tokenId1); - expect(accepted).to.eql(expectedAccepted); - - const pending = await contract.pendingChildrenOf(tokenId1); - expect(pending).to.eql(expectedPending); - } -}); diff --git a/assets/eip-6065/corporate-structure.png b/assets/eip-6065/corporate-structure.png deleted file mode 100644 index 0d22567..0000000 Binary files a/assets/eip-6065/corporate-structure.png and /dev/null differ diff --git a/assets/eip-6123/README.md b/assets/eip-6123/README.md deleted file mode 100644 index 2bf456a..0000000 --- a/assets/eip-6123/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# SDC Solidity implementation - -## Description -This sdc implementation aims to implement process logic in a very lean way using an integrative solidity implementation and according unit tests - -### Provided Contracts and Tests -- `contracts/ISDC.sol` - Interface contract -- `contracts/SDC.sol` - SDC reference implementation contract -- `contracts/SDCToken.sol` - Mintable token contract for unit tests -- `test/SDC.js` - Unit tests for livecycle of sdc implementation - -### Used javascript based testing libraries for solidity -- `ethereum-waffle`: Waffle is a Solidity testing library. It allows you to write tests for your contracts with JavaScript. -- `chai`: Chai is an assertion library and provides functions like expect. -- `ethers`: This is a popular Ethereum client library. It allows you to interface with blockchains that implement the Ethereum API. -- `solidity-coverage`: This library gives you coverage reports on unit tests with the help of Istanbul. - -### Compile and run tests with hardhat -We provide the essential steps to compile the contracts and run provided unit tests -Check that you have the latest version of npm and node via `npm -version` (should be better than 8.5.0) and `node -v` (should be better than 16.14.2). - -1. Check out project -2. Go to folder and initialise a new npm project: `npm init -y`. A basic `package.json` file should occur -3. Install Hardhat as local solidity dev environment: `npx hardhat` -4. Select following option: Create an empty hardhat.config.js -5. Install Hardhat as a development dependency: `npm install --save-dev hardhat` -6. Install further testing dependencies: -`npm install --save-dev @nomiclabs/hardhat-waffle @nomiclabs/hardhat-ethers ethereum-waffle chai ethers solidity-coverage` -7. Install open zeppelin contracts: `npm install @openzeppelin/contracts` -8. add plugins to hardhat.config.ts: -``` -require("@nomiclabs/hardhat-waffle"); -require('solidity-coverage'); -``` - -9. Adding commands to `package.json`: -``` -"scripts": { - "build": "hardhat compile", - "test:light": "hardhat test", - "test": "hardhat coverage" - }, -``` -9. run `npm run build` -10. run `npm run test` - - diff --git a/assets/eip-6123/contracts/ISDC.sol b/assets/eip-6123/contracts/ISDC.sol deleted file mode 100644 index 0dac189..0000000 --- a/assets/eip-6123/contracts/ISDC.sol +++ /dev/null @@ -1,190 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.7.0 <0.9.0; - -/*------------------------------------------- DESCRIPTION ---------------------------------------------------------------------------------------*/ - -/** - * @title ERC6123 Smart Derivative Contract - * @dev Interface specification for a Smart Derivative Contract, which specifies the post-trade live cycle of an OTC financial derivative in a completely deterministic way. - * Counterparty Risk is removed by construction. - * - * A Smart Derivative Contract is a deterministic settlement protocol which has economically the same behaviour as a collateralized OTC financial derivative. - * It aims is to remove many inefficiencies in collateralized OTC transactions and remove counterparty credit risk by construction. - * - * In contrast to a collateralized derivative contract based and collateral flows are netted. As result, the smart derivative contract generates a stream of - * reflecting the settlement of a referenced underlying. The settlement cash flows may be daily (which is the standard frequency in traditional markets) - * or at higher frequencies. - * With each settlement flow the change is the (discounting adjusted) net present value of the underlying contract is exchanged and the value of the contract is reset to zero. - * - * To automatically process settlement, counterparties need to provide sufficient prefunded margin amounts and termination fees at the - * beginning of each settlement cycle. Through a settlement cycle the margin amounts are locked. Simplified, the contract reverts the classical scheme of - * 1) underlying valuation, then 2) funding of a margin call to - * 1) pre-funding of a margin buffer (a token), then 2) settlement. - * - * A SDC automatically terminates the derivatives contract if there is insufficient pre-funding or if the settlement amount exceeds a - * prefunded margin balance. Beyond mutual termination is also intended by the function specification. - * - * Events and Functionality specify the entire live cycle: TradeInception, TradeConfirmation, TradeTermination, Margin-Account-Mechanics, Valuation and Settlement. - * - * The process can be described by time points and time-intervals which are associated with well defined states: - *

    - *
  1. t < T* (befrore incept). - *
  2. - *
  3. - * The process runs in cycles. Let i = 0,1,2,... denote the index of the cycle. Within each cycle there are times - * T_{i,0}, T_{i,1}, T_{i,2}, T_{i,3} with T_{i,1} = pre-funding of the Smart Contract, T_{i,2} = request valuation from oracle, T_{i,3} = perform settlement on given valuation, T_{i+1,0} = T_{i,3}. - *
  4. - *
  5. - * Given this time discretization the states are assigned to time points and time intervalls: - *
    - *
    Idle
    - *
    Before incept or after terminate
    - * - *
    Initiation
    - *
    T* < t < T_{0}, where T* is time of incept and T_{0} = T_{0,0}
    - * - *
    AwaitingFunding
    - *
    T_{i,0} < t < T_{i,1}
    - * - *
    Funding
    - *
    t = T_{i,1}
    - * - *
    AwaitingSettlement
    - *
    T_{i,1} < t < T_{i,2}
    - * - *
    ValuationAndSettlement
    - *
    T_{i,2} < t < T_{i,3}
    - * - *
    Settled
    - *
    t = T_{i,3}
    - *
    - *
  6. - *
- */ - -interface ISDC { - /*------------------------------------------- EVENTS ---------------------------------------------------------------------------------------*/ - /** - * @dev Emitted when a new trade is incepted from a eligible counterparty - * @param initiator is the address from which trade was incepted - * @param tradeId is the trade ID (e.g. generated internally) - * @param tradeData holding the trade parameters - */ - event TradeIncepted(address initiator, string tradeId, string tradeData); - - /** - * @dev Emitted when an incepted trade is confirmed by the opposite counterparty - * @param confirmer the confirming party - * @param tradeId the trade identifier - */ - event TradeConfirmed(address confirmer, string tradeId); - - /** - * @dev Emitted when a confirmed trade is set to active - e.g. when termination fee amounts are provided - * @param tradeId the trade identifier of the activated trade - */ - event TradeActivated(string tradeId); - - /** - * @dev Emitted when an active trade is terminated - * @param cause string holding the cause of the termination - */ - event TradeTerminated(string cause); - - /** - * @dev Emitted when funding phase is initiated - */ - event ProcessAwaitingFunding(); - - /** - * @dev Emitted when margin balance was updated and sufficient funding is provided - */ - event ProcessFunded(); - - /** - * @dev Emitted when a valuation and settlement is requested - * @param tradeData holding the stored trade data - * @param lastSettlementData holding the settlementdata from previous settlement (next settlement will be the increment of next valuation compared to former valuation) - */ - event ProcessSettlementRequest(string tradeData, string lastSettlementData); - - /** - * @dev Emitted when a settlement was processed succesfully - */ - event ProcessSettled(); - - /** - * @dev Emitted when a counterparty proactively requests an early termination of the underlying trade - * @param cpAddress the address of the requesting party - * @param tradeId the trade identifier which is supposed to be terminated - */ - event TradeTerminationRequest(address cpAddress, string tradeId); - - /** - * @dev Emitted when early termination request is confirmed by the opposite party - * @param cpAddress the party which confirms the trade termination - * @param tradeId the trade identifier which is supposed to be terminated - */ - event TradeTerminationConfirmed(address cpAddress, string tradeId); - - /*------------------------------------------- FUNCTIONALITY ---------------------------------------------------------------------------------------*/ - - /// Trade Inception - - /** - * @notice Handles trade inception, stores trade data - * @dev emits a {TradeIncepted} event - * @param _tradeData a description of the trade specification e.g. in xml format, suggested structure - see assets/eip-6123/doc/sample-tradedata-filestructure.xml - * @param _initialSettlementData the initial settlement data (e.g. initial market data at which trade was incepted) - */ - function inceptTrade(string memory _tradeData, string memory _initialSettlementData) external; - - /** - * @notice Performs a matching of provided trade data and settlement data - * @dev emits a {TradeConfirmed} event if trade data match - * @param _tradeData a description of the trade in sdc.xml, e.g. in xml format, suggested structure - see assets/eip-6123/doc/sample-tradedata-filestructure.xml - * @param _initialSettlementData the initial settlement data (e.g. initial market data at which trade was incepted) - */ - function confirmTrade(string memory _tradeData, string memory _initialSettlementData) external; - - /// Settlement Cycle: Prefunding - - /** - * @notice Called from outside to check and secure pre-funding. Terminate the trade if prefunding fails. - * @dev emits a {ProcessFunded} event if prefunding check is successful or a {TradeTerminated} event if prefunding check fails - */ - function initiatePrefunding() external; - - /// Settlement Cycle: Settlement - - /** - * @notice Called to trigger a (maybe external) valuation of the underlying contract and afterwards the according settlement process - * @dev emits a {ProcessSettlementRequest} - */ - function initiateSettlement() external; - - /** - * @notice Called from outside to trigger according settlement on chain-balances callback for initiateSettlement() event handler - * @dev emits a {ProcessSettled} if settlement is successful or {TradeTerminated} if settlement fails - * @param settlementAmount the settlement amount. If settlementAmount > 0 then receivingParty receives this amount from other party. If settlementAmount < 0 then other party receives -settlementAmount from receivingParty. - * @param settlementData. the tripple (product, previousSettlementData, settlementData) determines the settlementAmount. - */ - function performSettlement(int256 settlementAmount, string memory settlementData) external; - - /// Trade termination - - /** - * @notice Called from a counterparty to request a mutual termination - * @dev emits a {TradeTerminationRequest} - * @param tradeId the trade identifier which is supposed to be terminated - */ - function requestTradeTermination(string memory tradeId) external; - - /** - * @notice Called from a counterparty to confirm a termination, which will triggers a final settlement before trade gets inactive - * @dev emits a {TradeTerminationConfirmed} - * @param tradeId the trade identifier of the trade which is supposed to be terminated - */ - function confirmTradeTermination(string memory tradeId) external; -} - diff --git a/assets/eip-6123/contracts/SDC.sol b/assets/eip-6123/contracts/SDC.sol deleted file mode 100644 index 02616ea..0000000 --- a/assets/eip-6123/contracts/SDC.sol +++ /dev/null @@ -1,447 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.8.0 <0.9.0; - -import "./ISDC.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - - -/** - * @title Reference Implementation of ERC6123 - Smart Derivative Contract - * @notice This reference implementation is based on a finite state machine with predefined trade and process states (see enums below) - * Some comments on the implementation: - * - trade and process states are used in modifiers to check which function is able to be called at which state - * - trade data are stored in the contract - * - trade data matching is done in incept and confirm routine (comparing the hash of the provided data) - * - ERC-20 token is used for three participants: counterparty1 and counterparty2 and sdc - * - when prefunding is done sdc contract will hold agreed amounts and perform settlement on those - * - sdc also keeps track on internal balances for each counterparty - * - during prefunding sdc will transfer required amounts to its own balance - therefore sufficient approval is needed - * - upon termination all remaining 'locked' amounts will be transferred back to the counterparties -*/ - -contract SDC is ISDC { - /* - * Trade States - */ - enum TradeState { - - /* - * State before the trade is incepted. - */ - Inactive, - - /* - * Incepted: Trade data submitted by one party. Market data for initial valuation is set. - */ - Incepted, - - /* - * Confirmed: Trade data accepted by other party. - */ - Confirmed, - - /* - * Active (Confirmend + Prefunded Termination Fees). Will cycle through process states. - */ - Active, - - /* - * Terminated. - */ - Terminated - } - - /* - * Process States. t < T* (vor incept). The process runs in cycles. Let i = 0,1,2,... denote the index of the cycle. Within each cycle there are times - * T_{i,0}, T_{i,1}, T_{i,2}, T_{i,3} with T_{i,1} = pre-funding of the Smart Contract, T_{i,2} = request valuation from oracle, T_{i,3} = perform settlement on given valuation, T_{i+1,0} = T_{i,3}. - * Given this time discretization the states are assigned to time points and time intervalls: - * Idle: Before incept or after terminate - * Initiation: T* < t < T_{0}, where T* is time of incept and T_{0} = T_{0,0} - * AwaitingFunding: T_{i,0} < t < T_{i,1} - * Funding: t = T_{i,1} - * AwaitingSettlement: T_{i,1} < t < T_{i,2} - * ValuationAndSettlement: T_{i,2} < t < T_{i,3} - * Settled: t = T_{i,3} - */ - enum ProcessState { - /** - * @dev The process has not yet started or is terminated - */ - Idle, - /* - * @dev The process is initiated (incepted, but not yet completed confimation). Next: AwaitingFunding - */ - Initiation, - /* - * @dev Awaiiting preparation for funding the smart contract. Next: Funding - */ - AwaitingFunding, - /* - * @dev Prefunding the smart contract. Next: AwaitingSettlement - */ - Funding, - /* - * @dev The smart contract is completely funded and awaits settlement. Next: ValuationAndSettlement - */ - Funded, - /* - * @dev The settlement process is initiated. Next: Settled or InTermination - */ - ValuationAndSettlement, - /* - * @dev Termination started. - */ - InTermination - } - - struct MarginRequirement { - int256 buffer; - int256 terminationFee; - } - - /* - * Modifiers serve as guards whether at a specific process state a specific function can be called - */ - - modifier onlyCounterparty() { - require(msg.sender == party1 || msg.sender == party2, "You are not a counterparty."); _; - } - modifier onlyWhenTradeInactive() { - require(tradeState == TradeState.Inactive, "Trade state is not 'Inactive'."); _; - } - modifier onlyWhenTradeIncepted() { - require(tradeState == TradeState.Incepted, "Trade state is not 'Incepted'."); _; - } - modifier onlyWhenProcessAwaitingFunding() { - require(processState == ProcessState.AwaitingFunding, "Process state is not 'AwaitingFunding'."); _; - } - modifier onlyWhenProcessFundedAndTradeActive() { - require(processState == ProcessState.Funded && tradeState == TradeState.Active, "Process state is not 'Funded' or Trade is not 'Active'."); _; - } - modifier onlyWhenProcessValuationAndSettlement() { - require(processState == ProcessState.ValuationAndSettlement, "Process state is not 'ValuationAndSettlement'."); _; - } - TradeState private tradeState; - ProcessState private processState; - - address public party1; - address public party2; - address private immutable receivingPartyAddress; // Determine the receiver: Positive values are consider to be received by receivingPartyAddress. Negative values are received by the other counterparty. - - /* - * liquidityToken holds: - * - funding account of party1 - * - funding account of party2 - * - account for SDC (sum - this is split among parties by sdcBalances) - */ - IERC20 private liquidityToken; - - string private tradeID; - string private tradeData; - string private lastSettlementData; - - mapping(address => MarginRequirement) private marginRequirements; // Storage of M and P per counterparty address - mapping(uint256 => address) private pendingRequests; // Stores open request hashes for several requests: initiation, update and termination - - mapping(address => int256) private sdcBalances; // internal book-keeping: needed to track what part of the gross token balance is held for each party - - - bool private mutuallyTerminated = false; - - constructor( - address counterparty1, - address counterparty2, - address receivingParty, - address tokenAddress, - uint256 initialMarginRequirement, - uint256 initalTerminationFee - ) { - party1 = counterparty1; - party2 = counterparty2; - receivingPartyAddress = receivingParty; - liquidityToken = IERC20(tokenAddress); - tradeState = TradeState.Inactive; - processState = ProcessState.Idle; - marginRequirements[party1] = MarginRequirement(int256(initialMarginRequirement), int256(initalTerminationFee)); - marginRequirements[party2] = MarginRequirement(int256(initialMarginRequirement), int256(initalTerminationFee)); - sdcBalances[party1] = 0; - sdcBalances[party2] = 0; - } - - /* - * generates a hash from tradeData and generates a map entry in openRequests - * emits a TradeIncepted - * can be called only when TradeState = Incepted - */ - function inceptTrade(string memory _tradeData, string memory _initialSettlementData) external override onlyCounterparty onlyWhenTradeInactive - { - processState = ProcessState.Initiation; - tradeState = TradeState.Incepted; // Set TradeState to Incepted - - uint256 hash = uint256(keccak256(abi.encode(_tradeData, _initialSettlementData))); - pendingRequests[hash] = msg.sender; - tradeID = Strings.toString(hash); - tradeData = _tradeData; // Set trade data to enable querying already in inception state - lastSettlementData = _initialSettlementData; // Store settlement data to make them available for confirming party - - emit TradeIncepted(msg.sender, tradeID, _tradeData); - } - - /* - * generates a hash from tradeData and checks whether an open request can be found by the opposite party - * if so, data are stored and open request is deleted - * emits a TradeConfirmed - * can be called only when TradeState = Incepted - */ - function confirmTrade(string memory _tradeData, string memory _initialSettlementData) external override onlyCounterparty onlyWhenTradeIncepted - { - address pendingRequestParty = msg.sender == party1 ? party2 : party1; - uint256 tradeIDConf = uint256(keccak256(abi.encode(_tradeData, _initialSettlementData))); - require(pendingRequests[tradeIDConf] == pendingRequestParty, "Confirmation fails due to inconsistent trade data or wrong party address"); - delete pendingRequests[tradeIDConf]; // Delete Pending Request - - tradeState = TradeState.Confirmed; - emit TradeConfirmed(msg.sender, tradeID); - - // Pre-Conditions - if(_lockTerminationFees()) { - tradeState = TradeState.Active; - emit TradeActivated(tradeID); - - processState = ProcessState.AwaitingFunding; - emit ProcessAwaitingFunding(); - } - } - - /** - * Check sufficient balances and lock Termination Fees otherwise trade does not get activated - */ - function _lockTerminationFees() internal returns(bool) { - bool isAvailableParty1 = (liquidityToken.balanceOf(party1) >= uint(marginRequirements[party1].terminationFee)) && (liquidityToken.allowance(party1,address(this)) >= uint(marginRequirements[party1].terminationFee)); - bool isAvailableParty2 = (liquidityToken.balanceOf(party2) >= uint(marginRequirements[party2].terminationFee)) && (liquidityToken.allowance(party2,address(this)) >= uint(marginRequirements[party2].terminationFee)); - if (isAvailableParty1 && isAvailableParty2){ - liquidityToken.transferFrom(party1, address(this), uint(marginRequirements[party1].terminationFee)); // transfer termination fee party1 to sdc - liquidityToken.transferFrom(party2, address(this), uint(marginRequirements[party2].terminationFee)); // transfer termination fee party2 to sdc - adjustSDCBalances(marginRequirements[party1].terminationFee, marginRequirements[party2].terminationFee); // Update internal balances - return true; - } - else{ - tradeState == TradeState.Inactive; - processState = ProcessState.Idle; - emit TradeTerminated("Termination Fee could not be locked."); - return false; - } - } - - /* - * Failsafe: Free up accounts upon termination - */ - function _processTermination() internal { - liquidityToken.transfer(party1, uint256(sdcBalances[party1])); - liquidityToken.transfer(party2, uint256(sdcBalances[party2])); - - processState = ProcessState.Idle; - tradeState = TradeState.Inactive; - } - - /* - * Settlement Cycle - */ - - /* - * Send an Lock Request Event only when Process State = Funding - * Puts Process state to Margin Account Check - * can be called only when ProcessState = AwaitingFunding - */ - function initiatePrefunding() external override onlyWhenProcessAwaitingFunding { - processState = ProcessState.Funding; - - uint256 balanceParty1 = liquidityToken.balanceOf(party1); - uint256 balanceParty2 = liquidityToken.balanceOf(party2); - - /* Calculate gap amount for each party, i.e. residual between buffer and termination fee and actual balance */ - // max(M+P - sdcBalance,0) - uint gapAmountParty1 = marginRequirements[party1].buffer + marginRequirements[party1].terminationFee - sdcBalances[party1] > 0 ? uint(marginRequirements[party1].buffer + marginRequirements[party1].terminationFee - sdcBalances[party1]) : 0; - uint gapAmountParty2 = marginRequirements[party2].buffer + marginRequirements[party2].terminationFee - sdcBalances[party2] > 0 ? uint(marginRequirements[party2].buffer + marginRequirements[party2].terminationFee - sdcBalances[party2]) : 0; - - /* Good case: Balances are sufficient and token has enough approval */ - if ( (balanceParty1 >= gapAmountParty1 && liquidityToken.allowance(party1,address(this)) >= gapAmountParty1) && - (balanceParty2 >= gapAmountParty2 && liquidityToken.allowance(party2,address(this)) >= gapAmountParty2) ) { - liquidityToken.transferFrom(party1, address(this), gapAmountParty1); // Transfer of GapAmount to sdc contract - liquidityToken.transferFrom(party2, address(this), gapAmountParty2); // Transfer of GapAmount to sdc contract - processState = ProcessState.Funded; - adjustSDCBalances(int(gapAmountParty1),int(gapAmountParty2)); // Update internal balances - emit ProcessFunded(); - } - /* Party 1 - Bad case: Balances are insufficient or token has not enough approval */ - else if ( (balanceParty1 < gapAmountParty1 || liquidityToken.allowance(party1,address(this)) < gapAmountParty1) && - (balanceParty2 >= gapAmountParty2 && liquidityToken.allowance(party2,address(this)) >= gapAmountParty2) ) { - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - - adjustSDCBalances(-marginRequirements[party1].terminationFee,marginRequirements[party1].terminationFee); // Update internal balances - - _processTermination(); // Release all buffers - emit TradeTerminated("Termination caused by party1 due to insufficient prefunding"); - } - /* Party 2 - Bad case: Balances are insufficient or token has not enough approval */ - else if ( (balanceParty1 >= gapAmountParty1 && liquidityToken.allowance(party1,address(this)) >= gapAmountParty1) && - (balanceParty2 < gapAmountParty2 || liquidityToken.allowance(party2,address(this)) < gapAmountParty2) ) { - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - - adjustSDCBalances(marginRequirements[party2].terminationFee,-marginRequirements[party2].terminationFee); // Update internal balances - - _processTermination(); // Release all buffers - emit TradeTerminated("Termination caused by party2 due to insufficient prefunding"); - } - /* Both parties fail: Cross Transfer of Termination Fee */ - else { - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - // if ( (balanceParty1 < gapAmountParty1 || liquidityToken.allowance(party1,address(this)) < gapAmountParty1) && (balanceParty2 < gapAmountParty2 || liquidityToken.allowance(party2,address(this)) < gapAmountParty2) ) { tradeState = TradeState.Terminated; - adjustSDCBalances(marginRequirements[party2].terminationFee-marginRequirements[party1].terminationFee,marginRequirements[party1].terminationFee-marginRequirements[party2].terminationFee); // Update internal balances: Cross Booking of termination fee - - _processTermination(); // Release all buffers - emit TradeTerminated("Termination caused by both parties due to insufficient prefunding"); - } - } - - /* - * Settlement can be initiated when margin accounts are locked, a valuation request event is emitted containing tradeData and valuationViewParty - * Changes Process State to Valuation&Settlement - * can be called only when ProcessState = Funded and TradeState = Active - */ - function initiateSettlement() external override onlyCounterparty onlyWhenProcessFundedAndTradeActive - { - processState = ProcessState.ValuationAndSettlement; - emit ProcessSettlementRequest(tradeData, lastSettlementData); - } - - /* - * Performs a settelement only when processState is ValuationAndSettlement - * Puts process state to "inTransfer" - * Checks Settlement amount according to valuationViewParty: If SettlementAmount is > 0, valuationViewParty receives - * can be called only when ProcessState = ValuationAndSettlement - */ - function performSettlement(int256 settlementAmount, string memory settlementData) onlyWhenProcessValuationAndSettlement external override - { - lastSettlementData = settlementData; - address receivingParty = settlementAmount > 0 ? receivingPartyAddress : other(receivingPartyAddress); - address payingParty = other(receivingParty); - - bool noTermination = abs(settlementAmount) <= marginRequirements[payingParty].buffer; - int256 transferAmount = (noTermination == true) ? abs(settlementAmount) : marginRequirements[payingParty].buffer + marginRequirements[payingParty].terminationFee; // Override with Buffer and Termination Fee: Max Transfer - - if(receivingParty == party1) // Adjust internal Balances, only debit is booked on sdc balance as receiving party obtains transfer amount directly from sdc - adjustSDCBalances(0, -transferAmount); - else - adjustSDCBalances(-transferAmount, 0); - - liquidityToken.transfer(receivingParty, uint256(transferAmount)); // SDC contract performs transfer to receiving party - - if (noTermination) { // Regular Settlement - emit ProcessSettled(); - processState = ProcessState.AwaitingFunding; // Set ProcessState to 'AwaitingFunding' - } else { // Termination Event, buffer not sufficient, transfer margin buffer and termination fee and process termination - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - _processTermination(); // Transfer all locked amounts - emit TradeTerminated("Termination due to margin buffer exceedance"); - } - - if (mutuallyTerminated) { // Both counterparties agreed on a premature termination - processState = ProcessState.InTermination; - _processTermination(); - } - } - - /* - * End of Cycle - */ - - /* - * Can be called by a party for mutual termination - * Hash is generated an entry is put into pendingRequests - * TerminationRequest is emitted - * can be called only when ProcessState = Funded and TradeState = Active - */ - function requestTradeTermination(string memory _tradeID) external override onlyCounterparty onlyWhenProcessFundedAndTradeActive - { - require(keccak256(abi.encodePacked(tradeID)) == keccak256(abi.encodePacked(_tradeID)), "Trade ID mismatch"); - uint256 hash = uint256(keccak256(abi.encode(_tradeID, "terminate"))); - pendingRequests[hash] = msg.sender; - emit TradeTerminationRequest(msg.sender, _tradeID); - } - - /* - - * Same pattern as for initiation - * confirming party generates same hash, looks into pendingRequests, if entry is found with correct address, tradeState is put to terminated - * can be called only when ProcessState = Funded and TradeState = Active - */ - function confirmTradeTermination(string memory tradeId) external override onlyCounterparty onlyWhenProcessFundedAndTradeActive - { - address pendingRequestParty = msg.sender == party1 ? party2 : party1; - uint256 hashConfirm = uint256(keccak256(abi.encode(tradeId, "terminate"))); - require(pendingRequests[hashConfirm] == pendingRequestParty, "Confirmation of termination failed due to wrong party or missing request"); - delete pendingRequests[hashConfirm]; - mutuallyTerminated = true; - emit TradeTerminationConfirmed(msg.sender, tradeID); - } - - function adjustSDCBalances(int256 adjustmentAmountParty1, int256 adjustmentAmountParty2) internal { - if (adjustmentAmountParty1 < 0) - require(sdcBalances[party1] >= adjustmentAmountParty1, "SDC Balance Adjustment fails for Party1"); - if (adjustmentAmountParty2 < 0) - require(sdcBalances[party2] >= adjustmentAmountParty2, "SDC Balance Adjustment fails for Party2"); - sdcBalances[party1] = sdcBalances[party1] + adjustmentAmountParty1; - sdcBalances[party2] = sdcBalances[party2] + adjustmentAmountParty2; - } - - /* - * Utilities - */ - - /** - * Absolute value of an integer - */ - function abs(int x) private pure returns (int) { - return x >= 0 ? x : -x; - } - - /** - * Other party - */ - function other(address party) private view returns (address) { - return (party == party1 ? party2 : party1); - } - - function getTokenAddress() public view returns(address) { - return address(liquidityToken); - } - - function getTradeID() public view returns (string memory) { - return tradeID; - } - - function getTradeData() public view returns (string memory) { - return tradeData; - } - - - function getTradeState() public view returns (TradeState) { - return tradeState; - } - - function getProcessState() public view returns (ProcessState) { - return processState; - } - - function getOwnSdcBalance() public view returns (int256) { - return sdcBalances[msg.sender]; - } - - /**END OF FUNCTIONS WHICH ARE ONLY USED FOR TESTING PURPOSES */ -} \ No newline at end of file diff --git a/assets/eip-6123/contracts/SDCToken.sol b/assets/eip-6123/contracts/SDCToken.sol deleted file mode 100644 index 45de674..0000000 --- a/assets/eip-6123/contracts/SDCToken.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract SDCToken is ERC20{ - constructor() ERC20("SDCToken", "SDCT"){ - - } - - function mint(address to, uint256 amount) public{ - _mint(to,amount); - } -} \ No newline at end of file diff --git a/assets/eip-6123/doc/sample-tradedata-filestructure.xml b/assets/eip-6123/doc/sample-tradedata-filestructure.xml deleted file mode 100644 index c557553..0000000 --- a/assets/eip-6123/doc/sample-tradedata-filestructure.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - net.finmath - finmath-smart-derivative-contract - 0.1.8 - - - - - Counterparty 1 - party1 - - constant - 10000.0 - - - constant - 1000.0 - -
0x...
-
- - Counterparty 2 - party2 - - constant - 10000.0 - - - constant - 1000.0 - -
0x...
-
-
- - - 2011-12-13T10:15:30 - - - daily - 17:00 - - - xyz - - symbol1 - symbol2 - ... - - - - - party1 - - - - - - - - - CP1 - - - - CP2 - - 2022-12-13 - - - - - - -
\ No newline at end of file diff --git a/assets/eip-6123/doc/sdc_livecycle_sequence_diagram.png b/assets/eip-6123/doc/sdc_livecycle_sequence_diagram.png deleted file mode 100644 index 253bdfe..0000000 Binary files a/assets/eip-6123/doc/sdc_livecycle_sequence_diagram.png and /dev/null differ diff --git a/assets/eip-6123/doc/sdc_trade_and_process_states.png b/assets/eip-6123/doc/sdc_trade_and_process_states.png deleted file mode 100644 index f9e1a22..0000000 Binary files a/assets/eip-6123/doc/sdc_trade_and_process_states.png and /dev/null differ diff --git a/assets/eip-6123/test/SDC.js b/assets/eip-6123/test/SDC.js deleted file mode 100644 index 06b579b..0000000 --- a/assets/eip-6123/test/SDC.js +++ /dev/null @@ -1,128 +0,0 @@ -const { ethers } = require("hardhat"); -const { expect } = require("chai"); -const AbiCoder = ethers.utils.AbiCoder; -const Keccak256 = ethers.utils.keccak256; - -describe("Livecycle Unit-Tests for Smart Derivative Contract", () => { - - // Define objects for TradeState enum, since solidity enums cannot provide their member names... - const TradeState = { - Inactive: 0, - Incepted: 1, - Confirmed: 2, - Active: 3, - Terminated: 4, - }; - - const abiCoder = new AbiCoder(); - const trade_data = "here are the trade specification { - const [_tokenManager, _counterparty1, _counterparty2] = await ethers.getSigners(); - tokenManager = _tokenManager; - counterparty1 = _counterparty1; - counterparty2 = _counterparty2; - const ERC20Factory = await ethers.getContractFactory("SDCToken"); - const SDCFactory = await ethers.getContractFactory("SDC"); - token = await ERC20Factory.deploy(); - await token.deployed(); - sdc = await SDCFactory.deploy(counterparty1.address, counterparty2.address,counterparty1.address, token.address,marginBufferAmount,terminationFee); - await sdc.deployed(); - console.log("SDC Address: %s", sdc.address); - }); - - it("Initial minting and approvals for SDC", async () => { - await token.connect(counterparty1).mint(counterparty1.address,initialLiquidityBalance); - await token.connect(counterparty2).mint(counterparty2.address,initialLiquidityBalance); - await token.connect(counterparty1).approve(sdc.address,terminationFee+marginBufferAmount); - await token.connect(counterparty2).approve(sdc.address,terminationFee+marginBufferAmount); - let allowanceSDCParty1 = await token.connect(counterparty1).allowance(counterparty1.address, sdc.address); - let allowanceSDCParty2 = await token.connect(counterparty2).allowance(counterparty2.address, sdc.address); - await expect(allowanceSDCParty1).equal(terminationFee+marginBufferAmount); - }); - - it("Counterparty1 incepts a trade", async () => { - const incept_call = await sdc.connect(counterparty1).inceptTrade(trade_data,"initialMarketData"); - let tradeid = await sdc.connect(counterparty1).getTradeID(); - //console.log("TradeId: %s", tradeid); - await expect(incept_call).to.emit(sdc, "TradeIncepted").withArgs(counterparty1.address,tradeid,trade_data); - let trade_state = await sdc.connect(counterparty1).getTradeState(); - await expect(trade_state).equal(TradeState.Incepted); - }); - - - it("Counterparty2 confirms a trade", async () => { - const confirm_call = await sdc.connect(counterparty2).confirmTrade(trade_data,"initialMarketData"); - //console.log("TradeId: %s", await sdc.callStatic.getTradeState()); - let balanceSDC = await token.connect(counterparty2).balanceOf(sdc.address); - await expect(confirm_call).to.emit(sdc, "TradeConfirmed"); - await expect(balanceSDC).equal(2*terminationFee); - let trade_state = await sdc.connect(counterparty1).getTradeState(); - await expect(trade_state).equal(TradeState.Active); - }); - - it("Processing first prefunding phase", async () => { - const call = await sdc.connect(counterparty2).initiatePrefunding(); - let balanceSDC = await token.connect(counterparty2).balanceOf(sdc.address); - let balanceCP2 = await token.connect(counterparty2).balanceOf(counterparty2.address); - await expect(balanceSDC).equal(2*(terminationFee+marginBufferAmount)); - await expect(balanceCP2).equal(initialLiquidityBalance-(terminationFee+marginBufferAmount)); - await expect(call).to.emit(sdc, "ProcessFunded"); - }); - - it("Initiate and perform first successful settlement in favour to counterparty 1", async () => { - const callInitSettlement = await sdc.connect(counterparty2).initiateSettlement(); - await expect(callInitSettlement).to.emit(sdc, "ProcessSettlementRequest"); - let balanceCP1 = parseInt(await token.connect(counterparty1).balanceOf(counterparty1.address)); - let balanceCP2 = parseInt(await token.connect(counterparty2).balanceOf(counterparty1.address)); - const callPerformSettlement = await sdc.connect(counterparty2).performSettlement(settlementAmount1,"settlementData"); - await expect(callPerformSettlement).to.emit(sdc, "ProcessSettled"); - let balanceSDC_afterSettlement = await token.connect(counterparty2).balanceOf(sdc.address); - let balanceCP1_afterSettlement = await token.connect(counterparty1).balanceOf(counterparty1.address); - let balanceCP2_afterSettlement = await token.connect(counterparty2).balanceOf(counterparty2.address); - await expect(balanceSDC_afterSettlement).equal(2*(terminationFee+marginBufferAmount)-settlementAmount1); // SDC balance less settlement - await expect(balanceCP1_afterSettlement).equal(balanceCP1+settlementAmount1); // settlement in favour to CP1 - await expect(balanceCP2_afterSettlement).equal(balanceCP2); // CP2 balance is not touched as transfer is booked from SDC balance - }); - - it("Process successfully second prefunding phase successful ", async () => { - await token.connect(counterparty2).approve(sdc.address,settlementAmount1); // CP2 increases allowance - const call = await sdc.connect(counterparty1).initiatePrefunding(); //Prefunding: SDC transfers missing gap amount from CP2 - let balanceSDC = await token.connect(counterparty2).balanceOf(sdc.address); - let balanceCP2 = await token.connect(counterparty2).balanceOf(counterparty2.address); - await expect(balanceSDC).equal(2*(terminationFee+marginBufferAmount)); - await expect(balanceCP2).equal(initialLiquidityBalance-(terminationFee+marginBufferAmount)-settlementAmount1); - await expect(call).to.emit(sdc, "ProcessFunded"); - }); - - - it("Second settlement fails due to high transfer amount in favour to counteparty 2 - Trade terminates", async () => { - const callInitSettlement = await sdc.connect(counterparty2).initiateSettlement(); - await expect(callInitSettlement).to.emit(sdc, "ProcessSettlementRequest"); - const callPerformSettlement = await sdc.connect(counterparty2).performSettlement(settlementAmount2,"settlementData"); - await expect(callPerformSettlement).to.emit(sdc, "TradeTerminated"); - - let balanceSDC = parseInt(await token.connect(counterparty2).balanceOf(sdc.address)); - let balanceCP1 = await token.connect(counterparty1).balanceOf(counterparty1.address); - let balanceCP2 = await token.connect(counterparty2).balanceOf(counterparty2.address); - let expectedBalanceCP1 = initialLiquidityBalance + settlementAmount1 - (marginBufferAmount + terminationFee); //CP1 received settlementAmount1 and paid margin buffer and termination fee - let expectedBalanceCP2 = initialLiquidityBalance - settlementAmount1 + (marginBufferAmount + terminationFee); //CP2 paid settlementAmount1 and receives margin buffer and termination fee - await expect(balanceCP1).equal(expectedBalanceCP1); - await expect(balanceCP2).equal(expectedBalanceCP2); - await expect(balanceSDC).equal(0); - }); - - - -}); \ No newline at end of file diff --git a/assets/eip-6150/contracts/ERC6150.sol b/assets/eip-6150/contracts/ERC6150.sol deleted file mode 100644 index 5dd0dc2..0000000 --- a/assets/eip-6150/contracts/ERC6150.sol +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interfaces/IERC6150.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -abstract contract ERC6150 is ERC721, IERC6150 { - mapping(uint256 => uint256) private _parentOf; - mapping(uint256 => uint256[]) private _childrenOf; - mapping(uint256 => uint256) private _indexInChildrenArray; - - constructor( - string memory name_, - string memory symbol_ - ) ERC721(name_, symbol_) {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC721) returns (bool) { - return - interfaceId == type(IERC6150).interfaceId || - super.supportsInterface(interfaceId); - } - - function parentOf( - uint256 tokenId - ) public view virtual override returns (uint256 parentId) { - _requireMinted(tokenId); - parentId = _parentOf[tokenId]; - } - - function childrenOf( - uint256 tokenId - ) public view virtual override returns (uint256[] memory childrenIds) { - if (tokenId > 0) { - _requireMinted(tokenId); - } - childrenIds = _childrenOf[tokenId]; - } - - function isRoot( - uint256 tokenId - ) public view virtual override returns (bool) { - _requireMinted(tokenId); - return _parentOf[tokenId] == 0; - } - - function isLeaf( - uint256 tokenId - ) public view virtual override returns (bool) { - _requireMinted(tokenId); - return _childrenOf[tokenId].length == 0; - } - - function _getIndexInChildrenArray( - uint256 tokenId - ) internal view virtual returns (uint256) { - return _indexInChildrenArray[tokenId]; - } - - function _safeBatchMintWithParent( - address to, - uint256 parentId, - uint256[] memory tokenIds - ) internal virtual { - _safeBatchMintWithParent( - to, - parentId, - tokenIds, - new bytes[](tokenIds.length) - ); - } - - function _safeBatchMintWithParent( - address to, - uint256 parentId, - uint256[] memory tokenIds, - bytes[] memory datas - ) internal virtual { - require( - tokenIds.length == datas.length, - "ERC6150: tokenIds.length != datas.length" - ); - for (uint256 i = 0; i < tokenIds.length; i++) { - _safeMintWithParent(to, parentId, tokenIds[i], datas[i]); - } - } - - function _safeMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual { - _safeMintWithParent(to, parentId, tokenId, ""); - } - - function _safeMintWithParent( - address to, - uint256 parentId, - uint256 tokenId, - bytes memory data - ) internal virtual { - require(tokenId > 0, "ERC6150: tokenId is zero"); - if (parentId != 0) - require(_exists(parentId), "ERC6150: parentId doesn't exist"); - - _beforeMintWithParent(to, parentId, tokenId); - - _parentOf[tokenId] = parentId; - _indexInChildrenArray[tokenId] = _childrenOf[parentId].length; - _childrenOf[parentId].push(tokenId); - - _safeMint(to, tokenId, data); - emit Minted(msg.sender, to, parentId, tokenId); - - _afterMintWithParent(to, parentId, tokenId); - } - - function _safeBurn(uint256 tokenId) internal virtual { - require(_exists(tokenId), "ERC6150: tokenId doesn't exist"); - require(isLeaf(tokenId), "ERC6150: tokenId is not a leaf"); - - uint256 parent = _parentOf[tokenId]; - uint256 lastTokenIndex = _childrenOf[parent].length - 1; - uint256 targetTokenIndex = _indexInChildrenArray[tokenId]; - uint256 lastIndexToken = _childrenOf[parent][lastTokenIndex]; - if (lastTokenIndex > targetTokenIndex) { - _childrenOf[parent][targetTokenIndex] = lastIndexToken; - _indexInChildrenArray[lastIndexToken] = targetTokenIndex; - } - - delete _childrenOf[parent][lastIndexToken]; - delete _indexInChildrenArray[tokenId]; - delete _parentOf[tokenId]; - - _burn(tokenId); - } - - function _beforeMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual {} - - function _afterMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual {} -} diff --git a/assets/eip-6150/contracts/ERC6150AccessControl.sol b/assets/eip-6150/contracts/ERC6150AccessControl.sol deleted file mode 100644 index 97e43a0..0000000 --- a/assets/eip-6150/contracts/ERC6150AccessControl.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150AccessControl.sol"; - -abstract contract ERC6150AccessControl is ERC6150, IERC6150AccessControl { - mapping(address => mapping(uint256 => bool)) private _isAdminOf; - - function isAdminOf( - uint256 tokenId, - address account - ) public view virtual override returns (bool) { - return _isAdminOf[account][tokenId]; - } - - function canMintChildren( - uint256 parentId, - address account - ) public view virtual override returns (bool) { - return isAdminOf(parentId, account); - } - - function canBurnTokenByAccount( - uint256 tokenId, - address account - ) public view virtual override returns (bool) { - require(isLeaf(tokenId), "not a leaf token"); - return isAdminOf(tokenId, account); - } - - function _afterMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual override { - _isAdminOf[to][tokenId] = true; - } - - function _addAdmin(address admin, uint256 tokenId) internal virtual { - require(admin != address(0), "zero address"); - require(_exists(tokenId), "tokenId doesn't exist"); - _isAdminOf[admin][tokenId] = true; - } - - function _removeAdmin(address admin, uint256 tokenId) internal virtual { - require(_isAdminOf[admin][tokenId] == true, "not an admin"); - require(admin != ownerOf(tokenId), "cannot remove owner"); - _isAdminOf[admin][tokenId] = false; - } -} diff --git a/assets/eip-6150/contracts/ERC6150Burnable.sol b/assets/eip-6150/contracts/ERC6150Burnable.sol deleted file mode 100644 index aa0e3a9..0000000 --- a/assets/eip-6150/contracts/ERC6150Burnable.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150Burnable.sol"; - -abstract contract ERC6150Burnable is ERC6150, IERC6150Burnable { - function safeBurn(uint256 tokenId) public virtual override { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "ERC6150Burnable: caller is neither token owner nor approved" - ); - _safeBurn(tokenId); - } - - function safeBatchBurn(uint256[] memory tokenIds) public virtual override { - for (uint256 i = 0; i < tokenIds.length; i++) { - safeBurn(tokenIds[i]); - } - } -} diff --git a/assets/eip-6150/contracts/ERC6150Enumerable.sol b/assets/eip-6150/contracts/ERC6150Enumerable.sol deleted file mode 100644 index 32a9751..0000000 --- a/assets/eip-6150/contracts/ERC6150Enumerable.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150Enumerable.sol"; - -abstract contract ERC6150Enumerable is ERC6150, IERC6150Enumerable { - function childrenCountOf( - uint256 parentId - ) external view virtual override returns (uint256) { - return childrenOf(parentId).length; - } - - function childOfParentByIndex( - uint256 parentId, - uint256 index - ) external view virtual override returns (uint256) { - uint256[] memory children = childrenOf(parentId); - return children[index]; - } - - function indexInChildrenEnumeration( - uint256 parentId, - uint256 tokenId - ) external view virtual override returns (uint256) { - require(parentOf(tokenId) == parentId, "wrong parent"); - return _getIndexInChildrenArray(tokenId); - } -} diff --git a/assets/eip-6150/contracts/ERC6150ParentTransferable.sol b/assets/eip-6150/contracts/ERC6150ParentTransferable.sol deleted file mode 100644 index cb552e8..0000000 --- a/assets/eip-6150/contracts/ERC6150ParentTransferable.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150ParentTransferable.sol"; - -abstract contract ERC6150ParentTransferable is - ERC6150, - IERC6150ParentTransferable -{ - function transferParent( - uint256 newParentId, - uint256 tokenId - ) public virtual override { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "ERC6150ParentTransferable: caller is not token owner nor approved" - ); - if (newParentId != 0) { - require( - _exists(newParentId), - "ERC6150ParentTransferable: newParentId doesn't exists" - ); - } - - address owner = ownerOf(tokenId); - uint256 oldParentId = parentOf(tokenId); - _safeBurn(tokenId); - _safeMintWithParent(owner, newParentId, tokenId); - emit ParentTransferred(tokenId, oldParentId, newParentId); - } - - function batchTransferParent( - uint256 newParentId, - uint256[] memory tokenIds - ) public virtual override { - for (uint256 i = 0; i < tokenIds.length; i++) { - transferParent(tokenIds[i], newParentId); - } - } -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150.sol b/assets/eip-6150/contracts/interfaces/IERC6150.sol deleted file mode 100644 index 7da6ed5..0000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "IERC721.sol"; -import "IERC165.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0x897e2c73. - */ -interface IERC6150 is IERC721, IERC165 { - /** - * @notice Emitted when `tokenId` token under `parentId` is minted. - * @param minter The address of minter - * @param to The address received token - * @param parentId The id of parent token, if it's zero, it means minted `tokenId` is a root token. - * @param tokenId The id of minted token, required to be greater than zero - */ - event Minted( - address indexed minter, - address indexed to, - uint256 parentId, - uint256 tokenId - ); - - /** - * @notice Get the parent token of `tokenId` token. - * @param tokenId The child token - * @return parentId The Parent token found - */ - function parentOf(uint256 tokenId) external view returns (uint256 parentId); - - /** - * @notice Get the children tokens of `tokenId` token. - * @param tokenId The parent token - * @return childrenIds The array of children tokens - */ - function childrenOf( - uint256 tokenId - ) external view returns (uint256[] memory childrenIds); - - /** - * @notice Check the `tokenId` token if it is a root token. - * @param tokenId The token want to be checked - * @return Return `true` if it is a root token; if not, return `false` - */ - function isRoot(uint256 tokenId) external view returns (bool); - - /** - * @notice Check the `tokenId` token if it is a leaf token. - * @param tokenId The token want to be checked - * @return Return `true` if it is a leaf token; if not, return `false` - */ - function isLeaf(uint256 tokenId) external view returns (bool); -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150AccessControl.sol b/assets/eip-6150/contracts/interfaces/IERC6150AccessControl.sol deleted file mode 100644 index 661679d..0000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150AccessControl.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for access control - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0x1d04f0b3. - */ -interface IERC6150AccessControl is IERC6150 { - /** - * @notice Check the account whether a admin of `tokenId` token. - * @dev Each token can be set more than one admin. Admin have permission to do something to the token, like mint child token, - * or burn token, or transfer parentship. - * @param tokenId The specified token - * @param account The account to be checked - * @return If the account has admin permission, return true; otherwise, return false. - */ - function isAdminOf( - uint256 tokenId, - address account - ) external view returns (bool); - - /** - * @notice Check whether the specified parent token and account can mint children tokens - * @dev If the `parentId` is zero, check whether account can mint root nodes - * @param parentId The specified parent token to be checked - * @param account The specified account to be checked - * @return If the token and account has mint permission, return true; otherwise, return false. - */ - function canMintChildren( - uint256 parentId, - address account - ) external view returns (bool); - - /** - * @notice Check whether the specified token can be burnt by specified account - * @param tokenId The specified token to be checked - * @param account The specified account to be checked - * @return If the tokenId can be burnt by account, return true; otherwise, return false. - */ - function canBurnTokenByAccount( - uint256 tokenId, - address account - ) external view returns (bool); -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150Burnable.sol b/assets/eip-6150/contracts/interfaces/IERC6150Burnable.sol deleted file mode 100644 index 5cb8c9a..0000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150Burnable.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for burnable - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0x4ac0aa46. - */ -interface IERC6150Burnable is IERC6150 { - /** - * @notice Burn the `tokenId` token. - * @dev Throws if `tokenId` is not a leaf token. - * Throws if `tokenId` is not a valid NFT. - * Throws if `owner` is not the owner of `tokenId` token. - * Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this token. - * @param tokenId The token to be burnt - */ - function safeBurn(uint256 tokenId) external; - - /** - * @notice Batch burn tokens. - * @dev Throws if one of `tokenIds` is not a leaf token. - * Throws if one of `tokenIds` is not a valid NFT. - * Throws if `owner` is not the owner of all `tokenIds` tokens. - * Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for all `tokenIds`. - * @param tokenIds The tokens to be burnt - */ - function safeBatchBurn(uint256[] memory tokenIds) external; -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150Enumerable.sol b/assets/eip-6150/contracts/interfaces/IERC6150Enumerable.sol deleted file mode 100644 index 030e4d9..0000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150Enumerable.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; -import "IERC721Enumerable.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for enumerable - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0xba541a2e. - */ -interface IERC6150Enumerable is IERC6150, IERC721Enumerable { - /** - * @notice Get total amount of children tokens under `parentId` token. - * @dev If `parentId` is zero, it means get total amount of root tokens. - * @return The total amount of children tokens under `parentId` token. - */ - function childrenCountOf(uint256 parentId) external view returns (uint256); - - /** - * @notice Get the token at the specified index of all children tokens under `parentId` token. - * @dev If `parentId` is zero, it means get root token. - * @return The token ID at `index` of all chlidren tokens under `parentId` token. - */ - function childOfParentByIndex( - uint256 parentId, - uint256 index - ) external view returns (uint256); - - /** - * @notice Get the index position of specified token in the children enumeration under specified parent token. - * @dev Throws if the `tokenId` is not found in the children enumeration. - * If `parentId` is zero, means get root token index. - * @param parentId The parent token - * @param tokenId The specified token to be found - * @return The index position of `tokenId` found in the children enumeration - */ - function indexInChildrenEnumeration( - uint256 parentId, - uint256 tokenId - ) external view returns (uint256); -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150ParentTransferable.sol b/assets/eip-6150/contracts/interfaces/IERC6150ParentTransferable.sol deleted file mode 100644 index 2ecfa97..0000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150ParentTransferable.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for parent transferable - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0xfa574808. - */ -interface IERC6150ParentTransferable is IERC6150 { - /** - * @notice Emitted when the parent of `tokenId` token changed. - * @param tokenId The token changed - * @param oldParentId Previous parent token - * @param newParentId New parent token - */ - event ParentTransferred( - uint256 tokenId, - uint256 oldParentId, - uint256 newParentId - ); - - /** - * @notice Transfer parentship of `tokenId` token to a new parent token - * @param newParentId New parent token id - * @param tokenId The token to be changed - */ - function transferParent(uint256 newParentId, uint256 tokenId) external; - - /** - * @notice Batch transfer parentship of `tokenIds` to a new parent token - * @param newParentId New parent token id - * @param tokenIds Array of token ids to be changed - */ - function batchTransferParent( - uint256 newParentId, - uint256[] memory tokenIds - ) external; -} diff --git a/assets/eip-6150/linux-hierarchy.png b/assets/eip-6150/linux-hierarchy.png deleted file mode 100644 index 60ecc1f..0000000 Binary files a/assets/eip-6150/linux-hierarchy.png and /dev/null differ diff --git a/assets/eip-6150/website-hierarchy.png b/assets/eip-6150/website-hierarchy.png deleted file mode 100644 index 85215ec..0000000 Binary files a/assets/eip-6150/website-hierarchy.png and /dev/null differ diff --git a/assets/eip-6220/contracts/Catalog.sol b/assets/eip-6220/contracts/Catalog.sol deleted file mode 100644 index 45d522e..0000000 --- a/assets/eip-6220/contracts/Catalog.sol +++ /dev/null @@ -1,267 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "./ICatalog.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -error BadConfig(); -error IdZeroForbidden(); -error PartAlreadyExists(); -error PartDoesNotExist(); -error PartIsNotSlot(); -error ZeroLengthIdsPassed(); - -/** - * @title Catalog - * @author RMRK team - * @notice Catalog contract for RMRK equippable module. - */ -contract Catalog is ICatalog { - using Address for address; - - /** - * @notice Mapping of uint64 `partId` to Catalog `Part` struct - */ - mapping(uint64 => Part) private _parts; - - /** - * @notice Mapping of uint64 `partId` to boolean flag, indicating that a given `Part` can be equippable by any address - */ - mapping(uint64 => bool) private _isEquippableToAll; - - uint64[] private _partIds; - - string private _metadataURI; - string private _type; - - /** - * @notice Used to initialize the catalog. - * @param metadataURI Base metadata URI of the catalog - * @param type_ Type of catalog - */ - constructor(string memory metadataURI, string memory type_) { - _metadataURI = metadataURI; - _type = type_; - } - - /** - * @notice Used to limit execution of functions intended for the `Slot` parts to only execute when used with such - * parts. - * @dev Reverts execution of a function if the part with associated `partId` is uninitailized or is `Fixed`. - * @param partId ID of the part that we want the function to interact with - */ - modifier onlySlot(uint64 partId) { - _onlySlot(partId); - _; - } - - function _onlySlot(uint64 partId) private view { - ItemType itemType = _parts[partId].itemType; - if (itemType == ItemType.None) revert PartDoesNotExist(); - if (itemType == ItemType.Fixed) revert PartIsNotSlot(); - } - - /** - * @inheritdoc IERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - returns (bool) - { - return - interfaceId == type(IERC165).interfaceId || - interfaceId == type(ICatalog).interfaceId; - } - - /** - * @inheritdoc ICatalog - */ - function getMetadataURI() external view returns (string memory) { - return _metadataURI; - } - - /** - * @inheritdoc ICatalog - */ - function getType() external view returns (string memory) { - return _type; - } - - /** - * @notice Internal helper function that adds `Part` entries to storage. - * @dev Delegates to { _addPart } below. - * @param partIntake An array of `IntakeStruct` structs, consisting of `partId` and a nested `Part` struct - */ - function _addPartList(IntakeStruct[] calldata partIntake) internal { - uint256 len = partIntake.length; - for (uint256 i; i < len; ) { - _addPart(partIntake[i]); - unchecked { - ++i; - } - } - } - - /** - * @notice Internal function that adds a single `Part` to storage. - * @param partIntake `IntakeStruct` struct consisting of `partId` and a nested `Part` struct - * - */ - function _addPart(IntakeStruct calldata partIntake) internal { - uint64 partId = partIntake.partId; - Part memory part = partIntake.part; - - if (partId == uint64(0)) revert IdZeroForbidden(); - if (_parts[partId].itemType != ItemType.None) - revert PartAlreadyExists(); - if (part.itemType == ItemType.None) revert BadConfig(); - if (part.itemType == ItemType.Fixed && part.equippable.length != 0) - revert BadConfig(); - - _parts[partId] = part; - _partIds.push(partId); - - emit AddedPart( - partId, - part.itemType, - part.z, - part.equippable, - part.metadataURI - ); - } - - /** - * @notice Internal function used to add multiple `equippableAddresses` to a single catalog entry. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the `Part` that we are adding the equippable addresses to - * @param equippableAddresses An array of addresses that can be equipped into the `Part` associated with the `partId` - */ - function _addEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) internal onlySlot(partId) { - if (equippableAddresses.length <= 0) revert ZeroLengthIdsPassed(); - - uint256 len = equippableAddresses.length; - for (uint256 i; i < len; ) { - _parts[partId].equippable.push(equippableAddresses[i]); - unchecked { - ++i; - } - } - delete _isEquippableToAll[partId]; - - emit AddedEquippables(partId, equippableAddresses); - } - - /** - * @notice Internal function used to set the new list of `equippableAddresses`. - * @dev Overwrites existing `equippableAddresses`. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the `Part`s that we are overwiting the `equippableAddresses` for - * @param equippableAddresses A full array of addresses that can be equipped into this `Part` - */ - function _setEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) internal onlySlot(partId) { - if (equippableAddresses.length <= 0) revert ZeroLengthIdsPassed(); - _parts[partId].equippable = equippableAddresses; - delete _isEquippableToAll[partId]; - - emit SetEquippables(partId, equippableAddresses); - } - - /** - * @notice Internal function used to remove all of the `equippableAddresses` for a `Part` associated with the `partId`. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the part that we are clearing the `equippableAddresses` from - */ - function _resetEquippableAddresses(uint64 partId) - internal - onlySlot(partId) - { - delete _parts[partId].equippable; - delete _isEquippableToAll[partId]; - - emit SetEquippables(partId, new address[](0)); - } - - /** - * @notice Sets the isEquippableToAll flag to true, meaning that any collection may be equipped into the `Part` with this - * `partId`. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the `Part` that we are setting as equippable by any address - */ - function _setEquippableToAll(uint64 partId) internal onlySlot(partId) { - _isEquippableToAll[partId] = true; - emit SetEquippableToAll(partId); - } - - /** - * @inheritdoc ICatalog - */ - function checkIsEquippableToAll(uint64 partId) public view returns (bool) { - return _isEquippableToAll[partId]; - } - - /** - * @inheritdoc ICatalog - */ - function checkIsEquippable(uint64 partId, address targetAddress) - public - view - returns (bool) - { - // If this is equippable to all, we're good - bool isEquippable = _isEquippableToAll[partId]; - - // Otherwise, must check against each of the equippable for the part - if (!isEquippable && _parts[partId].itemType == ItemType.Slot) { - address[] memory equippable = _parts[partId].equippable; - uint256 len = equippable.length; - for (uint256 i; i < len; ) { - if (targetAddress == equippable[i]) { - isEquippable = true; - break; - } - unchecked { - ++i; - } - } - } - return isEquippable; - } - - /** - * @inheritdoc ICatalog - */ - function getPart(uint64 partId) public view returns (Part memory) { - return (_parts[partId]); - } - - /** - * @inheritdoc ICatalog - */ - function getParts(uint64[] calldata partIds) - public - view - returns (Part[] memory) - { - uint256 numParts = partIds.length; - Part[] memory parts = new Part[](numParts); - - for (uint256 i; i < numParts; ) { - uint64 partId = partIds[i]; - parts[i] = _parts[partId]; - unchecked { - ++i; - } - } - - return parts; - } -} diff --git a/assets/eip-6220/contracts/EquippableToken.sol b/assets/eip-6220/contracts/EquippableToken.sol deleted file mode 100644 index e880f23..0000000 --- a/assets/eip-6220/contracts/EquippableToken.sol +++ /dev/null @@ -1,2402 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//Generally all interactions should propagate downstream - -pragma solidity ^0.8.16; - -import "./ICatalog.sol"; -import "./IERC6220.sol"; -import "./IERC6059.sol"; -import "./library/EquippableLib.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -error ApprovalForAssetsToCurrentOwner(); -error ApproveForAssetsCallerIsNotOwnerNorApprovedForAll(); -error AssetAlreadyExists(); -error BadPriorityListLength(); -error CatalogRequiredForParts(); -error ChildAlreadyExists(); -error ChildIndexOutOfRange(); -error EquippableEquipNotAllowedByCatalog(); -error ERC721AddressZeroIsNotaValidOwner(); -error ERC721ApprovalToCurrentOwner(); -error ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); -error ERC721ApproveToCaller(); -error ERC721InvalidTokenId(); -error ERC721MintToTheZeroAddress(); -error ERC721NotApprovedOrOwner(); -error ERC721TokenAlreadyMinted(); -error ERC721TransferFromIncorrectOwner(); -error ERC721TransferToNonReceiverImplementer(); -error ERC721TransferToTheZeroAddress(); -error IdZeroForbidden(); -error IndexOutOfRange(); -error IsNotContract(); -error MaxPendingAssetsReached(); -error MaxPendingChildrenReached(); -error MaxRecursiveBurnsReached(address childContract, uint256 childId); -error MintToNonNestableImplementer(); -error MustUnequipFirst(); -error NestableTooDeep(); -error NestableTransferToDescendant(); -error NestableTransferToNonNestableImplementer(); -error NestableTransferToSelf(); -error NoAssetMatchingId(); -error NotApprovedForAssetsOrOwner(); -error NotApprovedOrDirectOwner(); -error NotEquipped(); -error PendingChildIndexOutOfRange(); -error SlotAlreadyUsed(); -error TargetAssetCannotReceiveSlot(); -error TokenCannotBeEquippedWithAssetIntoSlot(); -error TokenDoesNotHaveAsset(); -error UnexpectedAssetId(); -error UnexpectedChildId(); -error UnexpectedNumberOfAssets(); -error UnexpectedNumberOfChildren(); - -/** - * @title EquippableToken - * @author RMRK team - * @notice Smart contract of the Equippable module. - */ -contract EquippableToken is - Context, - IERC165, - IERC721, - IERC6059, - IERC6220 -{ - using Address for address; - using EquippableLib for uint64[]; - - // ----------------- ERC721 ------------- - - // Mapping owner address to token count - mapping(address => uint256) private _balances; - - // Mapping from token ID to approver address to approved address - // The approver is necessary so approvals are invalidated for nested children on transfer - // WARNING: If a child NFT returns to a previous root owner, old permissions would be active again - mapping(uint256 => mapping(address => address)) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // ----------------- MULTIASSETS ------------- - - /// Mapping of uint64 Ids to asset metadata - mapping(uint64 => string) private _assets; - - /// Mapping of tokenId to new asset, to asset to be replaced - mapping(uint256 => mapping(uint64 => uint64)) private _assetReplacements; - - /// Mapping of tokenId to an array of active assets - /// @dev Active recurses is unbounded, getting all would reach gas limit at around 30k items - /// so we leave this as internal in case a custom implementation needs to implement pagination - mapping(uint256 => uint64[]) internal _activeAssets; - - /// Mapping of tokenId to an array of pending assets - mapping(uint256 => uint64[]) internal _pendingAssets; - - /// Mapping of tokenId to an array of priorities for active assets - mapping(uint256 => uint16[]) internal _activeAssetPriorities; - - /// Mapping of tokenId to assetId to whether the token has this asset assigned - mapping(uint256 => mapping(uint64 => bool)) private _tokenAssets; - - /// Mapping from owner to operator approvals for assets - mapping(address => mapping(address => bool)) - private _operatorApprovalsForAssets; - - /** - * @notice Mapping from token ID to approver address to approved address for assets. - * @dev The approver is necessary so approvals are invalidated for nested children on transfer. - * @dev WARNING: If a child NFT returns the original root owner, old permissions would be active again. - */ - mapping(uint256 => mapping(address => address)) - private _tokenApprovalsForAssets; - - // ------------------- NESTABLE -------------- - - uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100; - - // Mapping from token ID to DirectOwner struct - mapping(uint256 => DirectOwner) private _directOwners; - - // Mapping of tokenId to array of active children structs - mapping(uint256 => Child[]) private _activeChildren; - - // Mapping of tokenId to array of pending children structs - mapping(uint256 => Child[]) private _pendingChildren; - - // Mapping of child token address to child token ID to whether they are pending or active on any token - // We might have a first extra mapping from token ID, but since the same child cannot be nested into multiple tokens - // we can strip it for size/gas savings. - mapping(address => mapping(uint256 => uint256)) private _childIsInActive; - - // ------------------- EQUIPPABLE -------------- - - /// Mapping of uint64 asset ID to corresponding catalog address. - mapping(uint64 => address) private _catalogAddresses; - /// Mapping of uint64 ID to asset object. - mapping(uint64 => uint64) private _equippableGroupIds; - /// Mapping of assetId to catalog parts applicable to this asset, both fixed and slot - mapping(uint64 => uint64[]) private _partIds; - - /// Mapping of token ID to catalog address to slot part ID to equipment information. Used to compose an NFT. - mapping(uint256 => mapping(address => mapping(uint64 => Equipment))) - private _equipments; - - /// Mapping of token ID to child (nestable) address to child ID to count of equipped items. Used to check if equipped. - mapping(uint256 => mapping(address => mapping(uint256 => uint8))) - private _equipCountPerChild; - - /// Mapping of `equippableGroupId` to parent contract address and valid `slotId`. - mapping(uint64 => mapping(address => uint64)) private _validParentSlots; - - // -------------------------- MODIFIERS ---------------------------- - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrOwner(uint256 tokenId) { - _onlyApprovedOrOwner(tokenId); - _; - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or is its direct owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrDirectOwner(uint256 tokenId) { - _onlyApprovedOrDirectOwner(tokenId); - _; - } - - /** - * @notice Used to ensure that the caller is either the owner of the given token or approved to manage the token's assets - * of the owner. - * @dev If that is not the case, the execution of the function will be reverted. - * @param tokenId ID of the token that we are checking - */ - modifier onlyApprovedForAssetsOrOwner(uint256 tokenId) { - _onlyApprovedForAssetsOrOwner(tokenId); - _; - } - - // --------------------- ERC721 GETTERS --------------------- - - /** - * @notice Used to retrieve the root owner of the given token. - * @dev Root owner is always the externally owned account. - * @dev If the given token is owned by another token, it will recursively query the parent tokens until reaching the - * root owner. - * @param tokenId ID of the token for which the root owner is being retrieved - * @return address Address of the root owner of the given token - */ - function ownerOf( - uint256 tokenId - ) public view virtual override(IERC6059, IERC721) returns (address) { - (address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf( - tokenId - ); - if (isNft) { - owner = IERC6059(owner).ownerOf(ownerTokenId); - } - return owner; - } - - /** - * @inheritdoc IERC165 - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC165).interfaceId || - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC5773).interfaceId || - interfaceId == type(IERC6059).interfaceId || - interfaceId == type(IERC6220).interfaceId; - } - - /** - * @inheritdoc IERC721 - */ - function balanceOf(address owner) public view virtual returns (uint256) { - if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner(); - return _balances[owner]; - } - - /** - * @inheritdoc IERC721 - */ - function getApproved( - uint256 tokenId - ) public view virtual returns (address) { - _requireMinted(tokenId); - - return _tokenApprovals[tokenId][ownerOf(tokenId)]; - } - - /** - * @inheritdoc IERC721 - */ - function isApprovedForAll( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovals[owner][operator]; - } - - // --------------------- ERC721 SETTERS --------------------- - - /** - * @inheritdoc IERC721 - */ - function approve(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - if (to == owner) revert ERC721ApprovalToCurrentOwner(); - - if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) - revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); - - _approve(to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function setApprovalForAll(address operator, bool approved) public virtual { - if (_msgSender() == operator) revert ERC721ApproveToCaller(); - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @notice Used to burn a given token. - * @param tokenId ID of the token to burn - */ - function burn(uint256 tokenId) public virtual { - burn(tokenId, 0); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) public virtual onlyApprovedOrDirectOwner(tokenId) returns (uint256) { - return _burn(tokenId, maxChildrenBurns); - } - - /** - * @inheritdoc IERC721 - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _transfer(from, to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual { - safeTransferFrom(from, to, tokenId, ""); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _safeTransfer(from, to, tokenId, data); - } - - // --------------------- ERC721 INTERNAL --------------------- - - /** - * @notice Used to grant an approval to manage a given token. - * @dev Emits an {Approval} event. - * @param to Address to which the approval is being granted - * @param tokenId ID of the token for which the approval is being granted - */ - function _approve(address to, uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - _tokenApprovals[tokenId][owner] = to; - emit Approval(owner, to, tokenId); - } - - /** - * @notice Used to update the owner of the token and clear the approvals associated with the previous owner. - * @dev The `destinationId` should equal `0` if the new owner is an externally owned account. - * @param tokenId ID of the token being updated - * @param destinationId ID of the token to receive the given token - * @param to Address of account to receive the token - * @param isNft A boolean value signifying whether the new owner is a token (`true`) or externally owned account - * (`false`) - */ - function _updateOwnerAndClearApprovals( - uint256 tokenId, - uint256 destinationId, - address to, - bool isNft - ) internal { - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId, - isNft: isNft - }); - - // Clear approvals from the previous owner - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - } - - /** - * @notice Used to enforce that the given token has been minted. - * @dev Reverts if the `tokenId` has not been minted yet. - * @dev The validation checks whether the owner of a given token is a `0x0` address and considers it not minted if - * it is. This means that both tokens that haven't been minted yet as well as the ones that have already been - * burned will cause the transaction to be reverted. - * @param tokenId ID of the token to check - */ - function _requireMinted(uint256 tokenId) internal view virtual { - if (!_exists(tokenId)) revert ERC721InvalidTokenId(); - } - - /** - * @notice Used to check whether the given token exists. - * @dev Tokens start existing when they are minted (`_mint`) and stop existing when they are burned (`_burn`). - * @param tokenId ID of the token being checked - * @return bool The boolean value signifying whether the token exists - */ - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _directOwners[tokenId].ownerAddress != address(0); - } - - /** - * @notice Used to invoke {IERC721Receiver-onERC721Received} on a target address. - * @dev The call is not executed if the target address is not a contract. - * @param from Address representing the previous owner of the given token - * @param to Yarget address that will receive the tokens - * @param tokenId ID of the token to be transferred - * @param data Optional data to send along with the call - * @return bool Boolean value signifying whether the call correctly returned the expected magic value - */ - function _checkOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory data - ) private returns (bool) { - if (to.isContract()) { - try - IERC721Receiver(to).onERC721Received( - _msgSender(), - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert ERC721TransferToNonReceiverImplementer(); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - /** - * @notice Used to safely mint a token to a specified address. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param to Address to which to safely mint the gven token - * @param tokenId ID of the token to mint to the specified address - */ - function _safeMint(address to, uint256 tokenId) internal virtual { - _safeMint(to, tokenId, ""); - } - - /** - * @notice Used to safely mint the token to the specified address while passing the additional data to contract - * recipients. - * @param to Address to which to mint the token - * @param tokenId ID of the token to mint - * @param data Additional data to send with the tokens - */ - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _mint(to, tokenId); - if (!_checkOnERC721Received(address(0), to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to mint a specified token to a given address. - * @dev WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - `to` cannot be the zero address. - * @dev Emits a {Transfer} event. - * @param to Address to mint the token to - * @param tokenId ID of the token to mint - */ - function _mint(address to, uint256 tokenId) internal virtual { - _innerMint(to, tokenId, 0); - - emit Transfer(address(0), to, tokenId); - emit NestTransfer(address(0), to, 0, 0, tokenId); - - _afterTokenTransfer(address(0), to, tokenId); - _afterNestedTokenTransfer(address(0), to, 0, 0, tokenId); - } - - /** - * @notice Used to mint a child token to a given parent token. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new child token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestMint( - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert MintToNonNestableImplementer(); - - _innerMint(to, tokenId, destinationId); - _sendToNFT(address(0), to, 0, destinationId, tokenId, data); - } - - /** - * @notice Used to mint a child token into a given parent token. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` must not exist. - * - `tokenId` must not be `0`. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new token - */ - function _innerMint( - address to, - uint256 tokenId, - uint256 destinationId - ) private { - if (to == address(0)) revert ERC721MintToTheZeroAddress(); - if (_exists(tokenId)) revert ERC721TokenAlreadyMinted(); - if (tokenId == 0) revert IdZeroForbidden(); - - _beforeTokenTransfer(address(0), to, tokenId); - _beforeNestedTokenTransfer(address(0), to, 0, destinationId, tokenId); - - _balances[to] += 1; - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId, - isNft: destinationId != 0 - }); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @dev Emits a {NestTransfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function _burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) internal virtual returns (uint256) { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - address owner = ownerOf(tokenId); - _balances[immediateOwner] -= 1; - - _beforeTokenTransfer(owner, address(0), tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - - Child[] memory children = childrenOf(tokenId); - - delete _activeChildren[tokenId]; - delete _pendingChildren[tokenId]; - delete _tokenApprovals[tokenId][owner]; - - uint256 pendingRecursiveBurns; - uint256 totalChildBurns; - - uint256 length = children.length; //gas savings - for (uint256 i; i < length; ) { - if (totalChildBurns >= maxChildrenBurns) - revert MaxRecursiveBurnsReached( - children[i].contractAddress, - children[i].tokenId - ); - delete _childIsInActive[children[i].contractAddress][ - children[i].tokenId - ]; - unchecked { - // At this point we know pendingRecursiveBurns must be at least 1 - pendingRecursiveBurns = maxChildrenBurns - totalChildBurns; - } - // We substract one to the next level to count for the token being burned, then add it again on returns - // This is to allow the behavior of 0 recursive burns meaning only the current token is deleted. - totalChildBurns += - IERC6059(children[i].contractAddress).burn( - children[i].tokenId, - pendingRecursiveBurns - 1 - ) + - 1; - unchecked { - ++i; - } - } - // Can't remove before burning child since child will call back to get root owner - delete _directOwners[tokenId]; - - _afterTokenTransfer(owner, address(0), tokenId); - _afterNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - emit Transfer(owner, address(0), tokenId); - emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId); - - return totalChildBurns; - } - - /** - * @notice Used to safely transfer the token form `from` to `to`. - * @dev The function checks that contract recipients are aware of the ERC721 protocol to prevent tokens from being - * forever locked. - * @dev This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. implement alternative - * mechanisms to perform token transfer, such as signature-based. - * @dev Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - * @param data Additional data with no specified format, sent in call to `to` - */ - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _transfer(from, to, tokenId); - if (!_checkOnERC721Received(from, to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to transfer the token from `from` to `to`. - * @dev As opposed to {transferFrom}, this imposes no restrictions on msg.sender. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - */ - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, 0, to, false); - _balances[to] += 1; - - emit Transfer(from, to, tokenId); - emit NestTransfer(immediateOwner, to, parentId, 0, tokenId); - - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - } - - // --------------------- NESTABLE GETTERS --------------------- - - /** - * @notice Used to retrieve the immediate owner of the given token. - * @dev In the event the NFT is owned by an externally owned account, `tokenId` will be `0` and `isNft` will be - * `false`. - * @param tokenId ID of the token for which the immediate owner is being retrieved - * @return address Address of the immediate owner. If the token is owned by an externally owned account, its address - * will be returned. If the token is owned by another token, the parent token's collection smart contract address - * is returned - * @return uint256 Token ID of the immediate owner. If the immediate owner is an externally owned account, the value - * should be `0` - * @return bool A boolean value signifying whether the immediate owner is a token (`true`) or not (`false`) - */ - function directOwnerOf( - uint256 tokenId - ) public view virtual returns (address, uint256, bool) { - DirectOwner memory owner = _directOwners[tokenId]; - if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId(); - - return (owner.ownerAddress, owner.tokenId, owner.isNft); - } - - /** - * @notice Used to retrieve the active child tokens of a given parent token. - * @dev Returns array of Child structs existing for parent token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the active child tokens - * @return struct[] An array of Child structs containing the parent token's active child tokens - */ - - function childrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory children = _activeChildren[parentId]; - return children; - } - - /** - * @notice Used to retrieve the pending child tokens of a given parent token. - * @dev Returns array of pending Child structs existing for given parent. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the pending child tokens - * @return struct[] An array of Child structs containing the parent token's pending child tokens - */ - - function pendingChildrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory pendingChildren = _pendingChildren[parentId]; - return pendingChildren; - } - - /** - * @notice Used to retrieve a specific active child token for a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the child is being retrieved - * @param index Index of the child token in the parent token's active child tokens array - * @return struct A Child struct containing data about the specified child - */ - function childOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (childrenOf(parentId).length <= index) revert ChildIndexOutOfRange(); - Child memory child = _activeChildren[parentId][index]; - return child; - } - - /** - * @notice Used to retrieve a specific pending child token from a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the pending child token is being retrieved - * @param index Index of the child token in the parent token's pending child tokens array - * @return struct A Child struct containting data about the specified child - */ - function pendingChildOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (pendingChildrenOf(parentId).length <= index) - revert PendingChildIndexOutOfRange(); - Child memory child = _pendingChildren[parentId][index]; - return child; - } - - /** - * @notice Used to verify that the given child tokwn is included in an active array of a token. - * @param childAddress Address of the given token's collection smart contract - * @param childId ID of the child token being checked - * @return bool A boolean value signifying whether the given child token is included in an active child tokens array - * of a token (`true`) or not (`false`) - */ - function childIsInActive( - address childAddress, - uint256 childId - ) public view virtual returns (bool) { - return _childIsInActive[childAddress][childId] != 0; - } - - // --------------------- NESTABLE SETTERS --------------------- - /** - * @notice Used to add a child token to a given parent token. - * @dev This adds the iichild token into the given parent token's pending child tokens array. - * @dev You MUST NOT call this method directly. To add a a child to an NFT you must use either - * `nestTransfer`, `nestMint` or `transferChild` to the NFT. - * @dev Requirements: - * - * - `ownerOf` on the child contract must resolve to the called contract. - * - The pending array of the parent contract must not be full. - * @param parentId ID of the parent token to receive the new child token - * @param childId ID of the new proposed child token - * @param data Additional data with no specified format - */ - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) public virtual { - _requireMinted(parentId); - - address childAddress = _msgSender(); - if (!childAddress.isContract()) revert IsNotContract(); - - Child memory child = Child({ - contractAddress: childAddress, - tokenId: childId - }); - - _beforeAddChild(parentId, childAddress, childId); - - uint256 length = pendingChildrenOf(parentId).length; - - if (length < 128) { - _pendingChildren[parentId].push(child); - } else { - revert MaxPendingChildrenReached(); - } - - // Previous length matches the index for the new child - emit ChildProposed(parentId, length, childAddress, childId); - - _afterAddChild(parentId, childAddress, childId); - } - - /** - * @notice @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) public virtual onlyApprovedOrOwner(parentId) { - _acceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @param tokenId ID of the parent token for which to reject all of the pending tokens - */ - function rejectAllChildren( - uint256 tokenId, - uint256 maxRejections - ) public virtual onlyApprovedOrOwner(tokenId) { - _rejectAllChildren(tokenId, maxRejections); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) public virtual onlyApprovedOrOwner(tokenId) { - _transferChild( - tokenId, - to, - destinationId, - childIndex, - childAddress, - childId, - isPending, - data - ); - } - - /** - * @notice Used to transfer the token into another token. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @param from Address of the direct owner of the token to be transferred - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token being transferred - * @param destinationId ID of the token to receive the token being transferred - */ - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _nestTransfer(from, to, tokenId, destinationId, data); - } - - // --------------------- NESTABLE INTERNAL --------------------- - - /** - * @notice Used to transfer a child token from a given parent token. - * @dev When transferring a child token, the owner of the token is set to `to`, or is not updated in the event of `to` - * being the `0x0` address. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits {ChildTransferred} event. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function _transferChild( - uint256 tokenId, - address to, - uint256 destinationId, // newParentId - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) internal virtual { - Child memory child; - if (!isPending) { - if (isChildEquipped(tokenId, childAddress, childId)) - revert MustUnequipFirst(); - } - if (isPending) { - child = pendingChildOf(tokenId, childIndex); - } else { - child = childOf(tokenId, childIndex); - } - _checkExpectedChild(child, childAddress, childId); - - _beforeTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - - if (isPending) { - _removeChildByIndex(_pendingChildren[tokenId], childIndex); - } else { - delete _childIsInActive[childAddress][childId]; - _removeChildByIndex(_activeChildren[tokenId], childIndex); - } - - if (to != address(0)) { - if (destinationId == 0) { - IERC721(childAddress).safeTransferFrom( - address(this), - to, - childId, - data - ); - } else { - // Destination is an NFT - IERC6059(child.contractAddress).nestTransferFrom( - address(this), - to, - child.tokenId, - destinationId, - data - ); - } - } - - emit ChildTransferred( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - _afterTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - } - - /** - * @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @dev Requirements: - * - * - `tokenId` must exist - * - `index` must be in range of the pending children array - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual { - if (pendingChildrenOf(parentId).length <= childIndex) - revert PendingChildIndexOutOfRange(); - - Child memory child = pendingChildOf(parentId, childIndex); - _checkExpectedChild(child, childAddress, childId); - if (_childIsInActive[childAddress][childId] != 0) - revert ChildAlreadyExists(); - - _beforeAcceptChild(parentId, childIndex, childAddress, childId); - - // Remove from pending: - _removeChildByIndex(_pendingChildren[parentId], childIndex); - - // Add to active: - _activeChildren[parentId].push(child); - _childIsInActive[childAddress][childId] = 1; // We use 1 as true - - emit ChildAccepted(parentId, childIndex, childAddress, childId); - - _afterAcceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @dev Requirements: - * - * - `tokenId` must exist - * @param tokenId ID of the parent token for which to reject all of the pending tokens. - * @param maxRejections Maximum number of expected children to reject, used to prevent from - * rejecting children which arrive just before this operation. - */ - function _rejectAllChildren( - uint256 tokenId, - uint256 maxRejections - ) internal virtual { - if (_pendingChildren[tokenId].length > maxRejections) - revert UnexpectedNumberOfChildren(); - - _beforeRejectAllChildren(tokenId); - delete _pendingChildren[tokenId]; - emit AllChildrenRejected(tokenId); - _afterRejectAllChildren(tokenId); - } - - function _checkExpectedChild( - Child memory child, - address expectedAddress, - uint256 expectedId - ) private pure { - if ( - expectedAddress != child.contractAddress || - expectedId != child.tokenId - ) revert UnexpectedChildId(); - } - - /** - * @notice Used to remove a specified child token form an array using its index within said array. - * @dev The caller must ensure that the length of the array is valid compared to the index passed. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param array An array od Child struct containing info about the child tokens in a given child tokens array - * @param index An index of the child token to remove in the accompanying array - */ - function _removeChildByIndex(Child[] storage array, uint256 index) private { - array[index] = array[array.length - 1]; - array.pop(); - } - - /** - * @notice Used to transfer a token into another token. - * @dev Attempting to nest a token into `0x0` address will result in reverted transaction. - * @dev Attempting to nest a token into itself will result in reverted transaction. - * @param from Address of the account currently owning the given token - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token to transfer - * @param destinationId ID of the token receiving the given token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestTransfer( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - if (to == address(this) && tokenId == destinationId) - revert NestableTransferToSelf(); - - // Destination contract checks: - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert NestableTransferToNonNestableImplementer(); - _checkForInheritanceLoop(tokenId, to, destinationId); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - to, - parentId, - destinationId, - tokenId - ); - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, destinationId, to, true); - _balances[to] += 1; - - // Sending to NFT: - _sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data); - } - - /** - * @notice Used to send a token to another token. - * @dev If the token being sent is currently owned by an externally owned account, the `parentId` should equal `0`. - * @dev Emits {Transfer} event. - * @dev Emits {NestTransfer} event. - * @param from Address from which the token is being sent - * @param to Address of the collection smart contract of the token to receive the given token - * @param parentId ID of the current parent token of the token being sent - * @param destinationId ID of the tokento receive the token being sent - * @param tokenId ID of the token being sent - * @param data Additional data with no specified format, sent in the addChild call - */ - function _sendToNFT( - address from, - address to, - uint256 parentId, - uint256 destinationId, - uint256 tokenId, - bytes memory data - ) private { - IERC6059 destContract = IERC6059(to); - destContract.addChild(destinationId, tokenId, data); - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(from, to, parentId, destinationId, tokenId); - - emit Transfer(from, to, tokenId); - emit NestTransfer(from, to, parentId, destinationId, tokenId); - } - - /** - * @notice Used to check if nesting a given token into a specified token would create an inheritance loop. - * @dev If a loop would occur, the tokens would be unmanageable, so the execution is reverted if one is detected. - * @dev The check for inheritance loop is bounded to guard against too much gas being consumed. - * @param currentId ID of the token that would be nested - * @param targetContract Address of the collection smart contract of the token into which the given token would be - * nested - * @param targetId ID of the token into which the given token would be nested - */ - function _checkForInheritanceLoop( - uint256 currentId, - address targetContract, - uint256 targetId - ) private view { - for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) { - ( - address nextOwner, - uint256 nextOwnerTokenId, - bool isNft - ) = IERC6059(targetContract).directOwnerOf(targetId); - // If there's a final address, we're good. There's no loop. - if (!isNft) { - return; - } - // Ff the current nft is an ancestor at some point, there is an inheritance loop - if (nextOwner == address(this) && nextOwnerTokenId == currentId) { - revert NestableTransferToDescendant(); - } - // We reuse the parameters to save some contract size - targetContract = nextOwner; - targetId = nextOwnerTokenId; - unchecked { - ++i; - } - } - revert NestableTooDeep(); - } - - // --------------------- MULTIASSET GETTERS --------------------- - - /** - * @notice Used to get the address of the user that is approved to manage the specified token from the current - * owner. - * @param tokenId ID of the token we are checking - * @return address Address of the account that is approved to manage the token - */ - function getApprovedForAssets( - uint256 tokenId - ) public view virtual returns (address) { - _requireMinted(tokenId); - return _tokenApprovalsForAssets[tokenId][ownerOf(tokenId)]; - } - - /** - * @inheritdoc IERC5773 - */ - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) public view virtual returns (string memory) { - if (!_tokenAssets[tokenId][assetId]) revert TokenDoesNotHaveAsset(); - return _assets[assetId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getActiveAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _activeAssets[tokenId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getPendingAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _pendingAssets[tokenId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getActiveAssetPriorities( - uint256 tokenId - ) public view virtual returns (uint16[] memory) { - return _activeAssetPriorities[tokenId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) public view virtual returns (uint64) { - return _assetReplacements[tokenId][newAssetId]; - } - - /** - * @inheritdoc IERC5773 - */ - function isApprovedForAllForAssets( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovalsForAssets[owner][operator]; - } - - // --------------------- MULTIASSET SETTERS --------------------- - /** - * @notice Used to grant approvals for specific tokens to a specified address. - * @dev This can only be called by the owner of the token or by an account that has been granted permission to - * manage all of the owner's assets. - * @param to Address of the account to receive the approval to the specified token - * @param tokenId ID of the token for which we are granting the permission - */ - function approveForAssets(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - if (to == owner) revert ApprovalForAssetsToCurrentOwner(); - - if ( - _msgSender() != owner && - !isApprovedForAllForAssets(owner, _msgSender()) - ) revert ApproveForAssetsCallerIsNotOwnerNorApprovedForAll(); - _approveForAssets(to, tokenId); - } - - /** - * @inheritdoc IERC5773 - */ - function setApprovalForAllForAssets( - address operator, - bool approved - ) public virtual { - address owner = _msgSender(); - if (owner == operator) revert ApprovalForAssetsToCurrentOwner(); - - _operatorApprovalsForAssets[owner][operator] = approved; - emit ApprovalForAllForAssets(owner, operator, approved); - } - - /** - * @notice Accepts a asset at from the pending array of given token. - * @dev Migrates the asset from the token's pending asset array to the token's active asset array. - * @dev Active assets cannot be removed by anyone, but can be replaced by a new asset. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - `index` must be in range of the length of the pending asset array. - * @dev Emits an {AssetAccepted} event. - * @param tokenId ID of the token for which to accept the pending asset - * @param index Index of the asset in the pending array to accept - */ - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _acceptAsset(tokenId, index, assetId); - } - - /** - * @notice Rejects a asset from the pending array of given token. - * @dev Removes the asset from the token's pending asset array. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - `index` must be in range of the length of the pending asset array. - * @dev Emits a {AssetRejected} event. - * @param tokenId ID of the token that the asset is being rejected from - * @param index Index of the asset in the pending array to be rejected - */ - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _rejectAsset(tokenId, index, assetId); - } - - /** - * @notice Rejects all assets from the pending array of a given token. - * @dev Effecitvely deletes the pending array. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * @dev Emits a {AssetRejected} event with assetId = 0. - * @param tokenId ID of the token of which to clear the pending array. - * @param maxRejections Maximum number of expected assets to reject, used to prevent from rejecting assets which - * arrive just before this operation. - */ - function rejectAllAssets( - uint256 tokenId, - uint256 maxRejections - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _rejectAllAssets(tokenId, maxRejections); - } - - /** - * @notice Sets a new priority array for a given token. - * @dev The priority array is a non-sequential list of `uint16`s, where the lowest value is considered highest - * priority. - * @dev Value `0` of a priority is a special case equivalent to unitialized. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - The length of `priorities` must be equal the length of the active assets array. - * @dev Emits a {AssetPrioritySet} event. - * @param tokenId ID of the token to set the priorities for - * @param priorities An array of priority values - */ - function setPriority( - uint256 tokenId, - uint16[] calldata priorities - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _setPriority(tokenId, priorities); - } - - // --------------------- MULTIASSET INTERNAL --------------------- - - /** - * @notice Internal function for granting approvals for a specific token. - * @param to Address of the account we are granting an approval to - * @param tokenId ID of the token we are granting the approval for - */ - function _approveForAssets(address to, uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - _tokenApprovalsForAssets[tokenId][owner] = to; - emit ApprovalForAssets(owner, to, tokenId); - } - - /** - * @notice Used to add a asset entry. - * @dev This internal function warrants custom access control to be implemented when used. - * @param id ID of the asset being added - * @param equippableGroupId ID of the equippable group being marked as equippable into the slot associated with - * `Parts` of the `Slot` type - * @param catalogAddress Address of the `Catalog` associated with the asset - * @param metadataURI The metadata URI of the asset - * @param partIds An array of IDs of fixed and slot parts to be included in the asset - */ - function _addAssetEntry( - uint64 id, - uint64 equippableGroupId, - address catalogAddress, - string memory metadataURI, - uint64[] calldata partIds - ) internal virtual { - _addAssetEntry(id, metadataURI); - - if (catalogAddress == address(0) && partIds.length != 0) - revert CatalogRequiredForParts(); - - _catalogAddresses[id] = catalogAddress; - _equippableGroupIds[id] = equippableGroupId; - _partIds[id] = partIds; - } - - /** - * @notice Used to accept a pending asset. - * @dev The call is reverted if there is no pending asset at a given index. - * @param tokenId ID of the token for which to accept the pending asset - * @param index Index of the asset in the pending array to accept - * @param assetId ID of the asset to accept in token's pending array - */ - function _acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) internal virtual { - _validatePendingAssetAtIndex(tokenId, index, assetId); - _beforeAcceptAsset(tokenId, index, assetId); - - uint64 replacesId = _assetReplacements[tokenId][assetId]; - uint256 replaceIndex; - bool replacefound; - if (replacesId != uint64(0)) - (replaceIndex, replacefound) = _activeAssets[tokenId].indexOf( - replacesId - ); - - if (replacefound) { - // We don't want to remove and then push a new asset. - // This way we also keep the priority of the original asset - _activeAssets[tokenId][replaceIndex] = assetId; - delete _tokenAssets[tokenId][replacesId]; - } else { - // We use the current size as next priority, by default priorities would be [0,1,2...] - _activeAssetPriorities[tokenId].push( - uint16(_activeAssets[tokenId].length) - ); - _activeAssets[tokenId].push(assetId); - replacesId = uint64(0); - } - _removePendingAsset(tokenId, index, assetId); - - emit AssetAccepted(tokenId, assetId, replacesId); - _afterAcceptAsset(tokenId, index, assetId); - } - - /** - * @notice Used to reject the specified asset from the pending array. - * @dev The call is reverted if there is no pending asset at a given index. - * @param tokenId ID of the token that the asset is being rejected from - * @param index Index of the asset in the pending array to be rejected - * @param assetId ID of the asset expected to be in the index - */ - function _rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) internal virtual { - _validatePendingAssetAtIndex(tokenId, index, assetId); - _beforeRejectAsset(tokenId, index, assetId); - - _removePendingAsset(tokenId, index, assetId); - delete _tokenAssets[tokenId][assetId]; - - emit AssetRejected(tokenId, assetId); - _afterRejectAsset(tokenId, index, assetId); - } - - /** - * @notice Used to validate the index on the pending assets array - * @dev The call is reverted if the index is out of range or the asset Id is not present at the index. - * @param tokenId ID of the token that the asset is validated from - * @param index Index of the asset in the pending array - * @param assetId Id of the asset expected to be in the index - */ - function _validatePendingAssetAtIndex( - uint256 tokenId, - uint256 index, - uint64 assetId - ) private view { - if (index >= _pendingAssets[tokenId].length) revert IndexOutOfRange(); - if (assetId != _pendingAssets[tokenId][index]) - revert UnexpectedAssetId(); - } - - /** - * @notice Used to remove the asset at the index on the pending assets array - * @param tokenId ID of the token that the asset is being removed from - * @param index Index of the asset in the pending array - * @param assetId Id of the asset expected to be in the index - */ - function _removePendingAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) private { - _pendingAssets[tokenId].removeItemByIndex(index); - delete _assetReplacements[tokenId][assetId]; - } - - /** - * @notice Used to reject all of the pending assets for the given token. - * @dev When rejecting all assets, the pending array is indiscriminately cleared. - * @dev If the number of pending assets is greater than the value of `maxRejections`, the exectuion will be - * reverted. - * @param tokenId ID of the token to reject all of the pending assets. - * @param maxRejections Maximum number of expected assets to reject, used to prevent from - * rejecting assets which arrive just before this operation. - */ - function _rejectAllAssets( - uint256 tokenId, - uint256 maxRejections - ) internal virtual { - uint256 len = _pendingAssets[tokenId].length; - if (len > maxRejections) revert UnexpectedNumberOfAssets(); - - _beforeRejectAllAssets(tokenId); - - for (uint256 i; i < len; ) { - uint64 assetId = _pendingAssets[tokenId][i]; - delete _assetReplacements[tokenId][assetId]; - unchecked { - ++i; - } - } - delete (_pendingAssets[tokenId]); - - emit AssetRejected(tokenId, uint64(0)); - _afterRejectAllAssets(tokenId); - } - - /** - * @notice Used to specify the priorities for a given token's active assets. - * @dev If the length of the priorities array doesn't match the length of the active assets array, the execution - * will be reverted. - * @dev The position of the priority value in the array corresponds the position of the asset in the active - * assets array it will be applied to. - * @param tokenId ID of the token for which the priorities are being set - * @param priorities Array of priorities for the assets - */ - function _setPriority( - uint256 tokenId, - uint16[] calldata priorities - ) internal virtual { - uint256 length = priorities.length; - if (length != _activeAssets[tokenId].length) - revert BadPriorityListLength(); - - _beforeSetPriority(tokenId, priorities); - _activeAssetPriorities[tokenId] = priorities; - - emit AssetPrioritySet(tokenId); - _afterSetPriority(tokenId, priorities); - } - - /** - * @notice Used to add an asset entry. - * @dev If the specified ID is already used by another asset, the execution will be reverted. - * @dev This internal function warrants custom access control to be implemented when used. - * @param id ID of the asset to assign to the new asset - * @param metadataURI Metadata URI of the asset - */ - function _addAssetEntry( - uint64 id, - string memory metadataURI - ) internal virtual { - if (id == uint64(0)) revert IdZeroForbidden(); - if (bytes(_assets[id]).length > 0) revert AssetAlreadyExists(); - - _beforeAddAsset(id, metadataURI); - _assets[id] = metadataURI; - - emit AssetSet(id); - _afterAddAsset(id, metadataURI); - } - - /** - * @notice Used to add an asset to a token. - * @dev If the given asset is already added to the token, the execution will be reverted. - * @dev If the asset ID is invalid, the execution will be reverted. - * @dev If the token already has the maximum amount of pending assets (128), the execution will be - * reverted. - * @param tokenId ID of the token to add the asset to - * @param assetId ID of the asset to add to the token - * @param replacesAssetWithId ID of the asset to replace from the token's list of active assets - */ - function _addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual { - if (_tokenAssets[tokenId][assetId]) revert AssetAlreadyExists(); - - if (bytes(_assets[assetId]).length == 0) revert NoAssetMatchingId(); - - if (_pendingAssets[tokenId].length >= 128) - revert MaxPendingAssetsReached(); - - _beforeAddAssetToToken(tokenId, assetId, replacesAssetWithId); - _tokenAssets[tokenId][assetId] = true; - _pendingAssets[tokenId].push(assetId); - - if (replacesAssetWithId != uint64(0)) { - _assetReplacements[tokenId][assetId] = replacesAssetWithId; - } - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - emit AssetAddedToTokens(tokenIds, assetId, replacesAssetWithId); - _afterAddAssetToToken(tokenId, assetId, replacesAssetWithId); - } - - // --------------------- EQUIPPABLE GETTERS --------------------- - - /** - * @inheritdoc IERC6220 - */ - function canTokenBeEquippedWithAssetIntoSlot( - address parent, - uint256 tokenId, - uint64 assetId, - uint64 slotId - ) public view virtual returns (bool) { - uint64 equippableGroupId = _equippableGroupIds[assetId]; - uint64 equippableSlot = _validParentSlots[equippableGroupId][parent]; - if (equippableSlot == slotId) { - (, bool found) = getActiveAssets(tokenId).indexOf(assetId); - return found; - } - return false; - } - - /** - * @inheritdoc IERC6220 - */ - function isChildEquipped( - uint256 tokenId, - address childAddress, - uint256 childId - ) public view virtual returns (bool) { - return _equipCountPerChild[tokenId][childAddress][childId] != uint8(0); - } - - /** - * @inheritdoc IERC6220 - */ - function getAssetAndEquippableData( - uint256 tokenId, - uint64 assetId - ) - public - view - virtual - returns (string memory, uint64, address, uint64[] memory) - { - return ( - getAssetMetadata(tokenId, assetId), - _equippableGroupIds[assetId], - _catalogAddresses[assetId], - _partIds[assetId] - ); - } - - /** - * @inheritdoc IERC6220 - */ - function getEquipment( - uint256 tokenId, - address targetCatalogAddress, - uint64 slotPartId - ) public view virtual returns (Equipment memory) { - return _equipments[tokenId][targetCatalogAddress][slotPartId]; - } - - // --------------------- EQUIPPABLE SETTERS --------------------- - - /** - * @inheritdoc IERC6220 - */ - function equip( - IntakeEquip memory data - ) public virtual onlyApprovedOrOwner(data.tokenId) { - _equip(data); - } - - /** - * @inheritdoc IERC6220 - */ - function unequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) public virtual onlyApprovedOrOwner(tokenId) { - _unequip(tokenId, assetId, slotPartId); - } - - // --------------------- EQUIPPABLE INTERNAL --------------------- - - /** - * @notice Private function used to equip a child into a token. - * @dev If the `Slot` already has an item equipped, the execution will be reverted. - * @dev If the child can't be used in the given `Slot`, the execution will be reverted. - * @dev If the catalog doesn't allow this equip to happen, the execution will be reverted. - * @dev The `IntakeEquip` stuct contains the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data An `IntakeEquip` struct specifying the equip data - */ - function _equip(IntakeEquip memory data) internal virtual { - address catalogAddress = _catalogAddresses[data.assetId]; - uint64 slotPartId = data.slotPartId; - if ( - _equipments[data.tokenId][catalogAddress][slotPartId] - .childEquippableAddress != address(0) - ) revert SlotAlreadyUsed(); - - // Check from parent's asset perspective: - (, bool found) = _partIds[data.assetId].indexOf(slotPartId); - if (!found) revert TargetAssetCannotReceiveSlot(); - - IERC6059.Child memory child = childOf(data.tokenId, data.childIndex); - - // Check from child perspective intention to be used in part - // We add reentrancy guard because of this call, it happens before updating state - if ( - !IERC6220(child.contractAddress) - .canTokenBeEquippedWithAssetIntoSlot( - address(this), - child.tokenId, - data.childAssetId, - slotPartId - ) - ) revert TokenCannotBeEquippedWithAssetIntoSlot(); - - // Check from catalog perspective - if ( - !ICatalog(catalogAddress).checkIsEquippable( - slotPartId, - child.contractAddress - ) - ) revert EquippableEquipNotAllowedByCatalog(); - - _beforeEquip(data); - Equipment memory newEquip = Equipment({ - assetId: data.assetId, - childAssetId: data.childAssetId, - childId: child.tokenId, - childEquippableAddress: child.contractAddress - }); - - _equipments[data.tokenId][catalogAddress][slotPartId] = newEquip; - _equipCountPerChild[data.tokenId][child.contractAddress][ - child.tokenId - ] += 1; - - emit ChildAssetEquipped( - data.tokenId, - data.assetId, - slotPartId, - child.tokenId, - child.contractAddress, - data.childAssetId - ); - _afterEquip(data); - } - - /** - * @notice Private function used to unequip child from parent token. - * @param tokenId ID of the parent from which the child is being unequipped - * @param assetId ID of the parent's asset that contains the `Slot` into which the child is equipped - * @param slotPartId ID of the `Slot` from which to unequip the child - */ - function _unequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) internal virtual { - address targetCatalogAddress = _catalogAddresses[assetId]; - Equipment memory equipment = _equipments[tokenId][targetCatalogAddress][ - slotPartId - ]; - if (equipment.childEquippableAddress == address(0)) - revert NotEquipped(); - _beforeUnequip(tokenId, assetId, slotPartId); - - delete _equipments[tokenId][targetCatalogAddress][slotPartId]; - _equipCountPerChild[tokenId][equipment.childEquippableAddress][ - equipment.childId - ] -= 1; - - emit ChildAssetUnequipped( - tokenId, - assetId, - slotPartId, - equipment.childId, - equipment.childEquippableAddress, - equipment.childAssetId - ); - _afterUnequip(tokenId, assetId, slotPartId); - } - - /** - * @notice Internal function used to declare that the assets belonging to a given `equippableGroupId` are - * equippable into the `Slot` associated with the `partId` of the collection at the specified `parentAddress` - * @param equippableGroupId ID of the equippable group - * @param parentAddress Address of the parent into which the equippable group can be equipped into - * @param slotPartId ID of the `Slot` that the items belonging to the equippable group can be equipped into - */ - function _setValidParentForEquippableGroup( - uint64 equippableGroupId, - address parentAddress, - uint64 slotPartId - ) internal virtual { - if (equippableGroupId == uint64(0) || slotPartId == uint64(0)) - revert IdZeroForbidden(); - _validParentSlots[equippableGroupId][parentAddress] = slotPartId; - emit ValidParentEquippableGroupIdSet( - equippableGroupId, - slotPartId, - parentAddress - ); - } - - // --------------------- MODIFIERS IMPLEMENTATIONS --------------------- - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @dev If the caller is not the owner of the token or approved to manage it by its owner, the execution will be - * reverted. - * @param tokenId ID of the token to check - */ - function _onlyApprovedOrOwner(uint256 tokenId) private view { - if (!_isApprovedOrOwner(_msgSender(), tokenId)) - revert ERC721NotApprovedOrOwner(); - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or it its direct owner. - * @dev This does not delegate to ownerOf, which returns the root owner, but rater uses an owner from DirectOwner - * struct. - * @dev The execution is reverted if the caller is not immediate owner or approved to manage the given token. - * @dev Used for parent-scoped transfers. - * @param tokenId ID of the token to check. - */ - function _onlyApprovedOrDirectOwner(uint256 tokenId) private view { - if (!_isApprovedOrDirectOwner(_msgSender(), tokenId)) - revert NotApprovedOrDirectOwner(); - } - - /** - * @notice Used to verify that the caller is either the owner of the given token or approved to manage the token's assets - * of the owner. - * @param tokenId ID of the token that we are checking - */ - function _onlyApprovedForAssetsOrOwner(uint256 tokenId) private view { - if (!_isApprovedForAssetsOrOwner(_msgSender(), tokenId)) - revert NotApprovedForAssetsOrOwner(); - } - - /** - * @notice Used to check whether the given account is allowed to manage the given token. - * @dev Requirements: - * - * - `tokenId` must exist. - * @param spender Address that is being checked for approval - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token - */ - function _isApprovedOrOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to check whether the account is approved to manage the token or its direct owner. - * @param spender Address that is being checked for approval or direct ownership - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token or its - * direct owner - */ - function _isApprovedOrDirectOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - (address owner, uint256 parentId, ) = directOwnerOf(tokenId); - // When the parent is an NFT, only it can do operations - if (parentId != 0) { - return (spender == owner); - } - // Otherwise, the owner or approved address can - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Internal function to check whether the queried user is either: - * 1. The root owner of the token associated with `tokenId`. - * 2. Is approved for all assets of the current owner via the `setApprovalForAllForAssets` function. - * 3. Is granted approval for the specific tokenId for asset management via the `approveForAssets` function. - * @param user Address of the user we are checking for permission - * @param tokenId ID of the token to query for permission for a given `user` - * @return bool A boolean value indicating whether the user is approved to manage the token or not - */ - function _isApprovedForAssetsOrOwner( - address user, - uint256 tokenId - ) internal view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (user == owner || - isApprovedForAllForAssets(owner, user) || - getApprovedForAssets(tokenId) == user); - } - - // --------------------- MULTIASSET HOOKS --------------------- - - /** - * @notice Hook that is called before an asset is added. - * @param id ID of the asset - * @param metadataURI Metadata URI of the asset - */ - function _beforeAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - /** - * @notice Hook that is called after an asset is added. - * @param id ID of the asset - * @param metadataURI Metadata URI of the asset - */ - function _afterAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - /** - * @notice Hook that is called before adding an asset to a token's pending assets array. - * @dev If the asset doesn't intend to replace another asset, the `replacesAssetWithId` value should be `0`. - * @param tokenId ID of the token to which the asset is being added - * @param assetId ID of the asset that is being added - * @param replacesAssetWithId ID of the asset that this asset is attempting to replace - */ - function _beforeAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - /** - * @notice Hook that is called after an asset has been added to a token's pending assets array. - * @dev If the asset doesn't intend to replace another asset, the `replacesAssetWithId` value should be `0`. - * @param tokenId ID of the token to which the asset is has been added - * @param assetId ID of the asset that is has been added - * @param replacesAssetWithId ID of the asset that this asset is attempting to replace - */ - function _afterAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - /** - * @notice Hook that is called before an asset is accepted to a token's active assets array. - * @param tokenId ID of the token for which the asset is being accepted - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to be located at the specified `index` - */ - function _beforeAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called after an asset is accepted to a token's active assets array. - * @param tokenId ID of the token for which the asset has been accepted - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to have been located at the specified `index` - */ - function _afterAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called before rejecting an asset. - * @param tokenId ID of the token from which the asset is being rejected - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to be located at the specified `index` - */ - function _beforeRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called after rejecting an asset. - * @param tokenId ID of the token from which the asset has been rejected - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to have been located at the specified `index` - */ - function _afterRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called before rejecting all assets of a token. - * @param tokenId ID of the token from which all of the assets are being rejected - */ - function _beforeRejectAllAssets(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called after rejecting all assets of a token. - * @param tokenId ID of the token from which all of the assets have been rejected - */ - function _afterRejectAllAssets(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called before the priorities for token's assets is set. - * @param tokenId ID of the token for which the asset priorities are being set - * @param priorities[] An array of priorities for token's active resources - */ - function _beforeSetPriority( - uint256 tokenId, - uint16[] calldata priorities - ) internal virtual {} - - /** - * @notice Hook that is called after the priorities for token's assets is set. - * @param tokenId ID of the token for which the asset priorities have been set - * @param priorities[] An array of priorities for token's active resources - */ - function _afterSetPriority( - uint256 tokenId, - uint16[] calldata priorities - ) internal virtual {} - - // --------------------- NESTABLE HOOKS --------------------- - - /** - * @notice Hook that is called before any token transfer. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be transferred to `to`. - * - When `from` is zero, `tokenId` will be minted to `to`. - * - When `to` is zero, ``from``'s `tokenId` will be burned. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after any transfer of tokens. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token has been transferred - * @param to Address to which the token has been transferred - * @param tokenId ID of the token that has been transferred - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param fromTokenId ID of the token from which the given token is being transferred - * @param toTokenId ID of the token to which the given token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token was transferred - * @param to Address to which the token was transferred - * @param fromTokenId ID of the token from which the given token was transferred - * @param toTokenId ID of the token to which the given token was transferred - * @param tokenId ID of the token that was transferred - */ - function _afterNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will receive a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has received a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _afterAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that will accept a pending child token - * @param childIndex Index of the child token to accept in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that has accepted a pending child token - * @param childIndex Index of the child token that was accpeted in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's pending children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's pending children array - */ - function _afterAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will transfer a child token - * @param childIndex Index of the child token that will be transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that is expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that is expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token is being transferred from the pending child - * tokens array (`true`) or from the active child tokens array (`false`) - */ - function _beforeTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called after a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has transferred a child token - * @param childIndex Index of the child token that was transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token was transferred from the pending child tokens - * array (`true`) or from the active child tokens array (`false`) - */ - function _afterTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called before a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will reject all of the pending child tokens - */ - function _beforeRejectAllChildren(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called after a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has rejected all of the pending child tokens - */ - function _afterRejectAllChildren(uint256 tokenId) internal virtual {} - - // --------------------- EQUIPPABLE HOOKS --------------------- - - /** - * @notice A hook to be called before a equipping a asset to the token. - * @dev The `IntakeEquip` struct consist of the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data The `IntakeEquip` struct containing data of the asset that is being equipped - */ - function _beforeEquip(IntakeEquip memory data) internal virtual {} - - /** - * @notice A hook to be called after equipping a asset to the token. - * @dev The `IntakeEquip` struct consist of the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data The `IntakeEquip` struct containing data of the asset that was equipped - */ - function _afterEquip(IntakeEquip memory data) internal virtual {} - - /** - * @notice A hook to be called before unequipping a asset from the token. - * @param tokenId ID of the token from which the asset is being unequipped - * @param assetId ID of the asset being unequipped - * @param slotPartId ID of the slot from which the asset is being unequipped - */ - function _beforeUnequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) internal virtual {} - - /** - * @notice A hook to be called after unequipping a asset from the token. - * @param tokenId ID of the token from which the asset was unequipped - * @param assetId ID of the asset that was unequipped - * @param slotPartId ID of the slot from which the asset was unequipped - */ - function _afterUnequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) internal virtual {} -} diff --git a/assets/eip-6220/contracts/ICatalog.sol b/assets/eip-6220/contracts/ICatalog.sol deleted file mode 100644 index ba91ebb..0000000 --- a/assets/eip-6220/contracts/ICatalog.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/** - * @title ICatalog - * @author RMRK team - * @notice An interface Catalog for equippable module. - */ -interface ICatalog is IERC165 { - /** - * @notice Event to announce addition of a new part. - * @dev It is emitted when a new part is added. - * @param partId ID of the part that was added - * @param itemType Enum value specifying whether the part is `None`, `Slot` and `Fixed` - * @param zIndex An uint specifying the z value of the part. It is used to specify the depth which the part should - * be rendered at - * @param equippableAddresses An array of addresses that can equip this part - * @param metadataURI The metadata URI of the part - */ - event AddedPart( - uint64 indexed partId, - ItemType indexed itemType, - uint8 zIndex, - address[] equippableAddresses, - string metadataURI - ); - - /** - * @notice Event to announce new equippables to the part. - * @dev It is emitted when new addresses are marked as equippable for `partId`. - * @param partId ID of the part that had new equippable addresses added - * @param equippableAddresses An array of the new addresses that can equip this part - */ - event AddedEquippables( - uint64 indexed partId, - address[] equippableAddresses - ); - - /** - * @notice Event to announce the overriding of equippable addresses of the part. - * @dev It is emitted when the existing list of addresses marked as equippable for `partId` is overwritten by a new - * one. - * @param partId ID of the part whose list of equippable addresses was overwritten - * @param equippableAddresses The new, full, list of addresses that can equip this part - */ - event SetEquippables(uint64 indexed partId, address[] equippableAddresses); - - /** - * @notice Event to announce that a given part can be equipped by any address. - * @dev It is emitted when a given part is marked as equippable by any. - * @param partId ID of the part marked as equippable by any address - */ - event SetEquippableToAll(uint64 indexed partId); - - /** - * @notice Used to define a type of the item. Possible values are `None`, `Slot` or `Fixed`. - * @dev Used for fixed and slot parts. - */ - enum ItemType { - None, - Slot, - Fixed - } - - /** - * @notice The integral structure of a standard RMRK catalog item defining it. - * @dev Requires a minimum of 3 storage slots per catalog item, equivalent to roughly 60,000 gas as of Berlin hard fork - * (April 14, 2021), though 5-7 storage slots is more realistic, given the standard length of an IPFS URI. This - * will result in between 25,000,000 and 35,000,000 gas per 250 assets--the maximum block size of Ethereum - * mainnet is 30M at peak usage. - * @return itemType The item type of the part - * @return z The z value of the part defining how it should be rendered when presenting the full NFT - * @return equippable The array of addresses allowed to be equipped in this part - * @return metadataURI The metadata URI of the part - */ - struct Part { - ItemType itemType; //1 byte - uint8 z; //1 byte - address[] equippable; //n Collections that can be equipped into this slot - string metadataURI; //n bytes 32+ - } - - /** - * @notice The structure used to add a new `Part`. - * @dev The part is added with specified ID, so you have to make sure that you are using an unused `partId`, - * otherwise the addition of the part vill be reverted. - * @dev The full `IntakeStruct` looks like this: - * [ - * partID, - * [ - * itemType, - * z, - * [ - * permittedCollectionAddress0, - * permittedCollectionAddress1, - * permittedCollectionAddress2 - * ], - * metadataURI - * ] - * ] - * @return partId ID to be assigned to the `Part` - * @return part A `Part` to be added - */ - struct IntakeStruct { - uint64 partId; - Part part; - } - - /** - * @notice Used to return the metadata URI of the associated catalog. - * @return string Base metadata URI - */ - function getMetadataURI() external view returns (string memory); - - /** - * @notice Used to return the `itemType` of the associated catalog - * @return string `itemType` of the associated catalog - */ - function getType() external view returns (string memory); - - /** - * @notice Used to check whether the given address is allowed to equip the desired `Part`. - * @dev Returns true if a collection may equip asset with `partId`. - * @param partId The ID of the part that we are checking - * @param targetAddress The address that we are checking for whether the part can be equipped into it or not - * @return bool The status indicating whether the `targetAddress` can be equipped into `Part` with `partId` or not - */ - function checkIsEquippable(uint64 partId, address targetAddress) - external - view - returns (bool); - - /** - * @notice Used to check if the part is equippable by all addresses. - * @dev Returns true if part is equippable to all. - * @param partId ID of the part that we are checking - * @return bool The status indicating whether the part with `partId` can be equipped by any address or not - */ - function checkIsEquippableToAll(uint64 partId) external view returns (bool); - - /** - * @notice Used to retrieve a `Part` with id `partId` - * @param partId ID of the part that we are retrieving - * @return struct The `Part` struct associated with given `partId` - */ - function getPart(uint64 partId) external view returns (Part memory); - - /** - * @notice Used to retrieve multiple parts at the same time. - * @param partIds An array of part IDs that we want to retrieve - * @return struct An array of `Part` structs associated with given `partIds` - */ - function getParts(uint64[] calldata partIds) - external - view - returns (Part[] memory); -} diff --git a/assets/eip-6220/contracts/IERC5773.sol b/assets/eip-6220/contracts/IERC5773.sol deleted file mode 100644 index 98ad0a9..0000000 --- a/assets/eip-6220/contracts/IERC5773.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5773 { - event AssetSet(uint64 assetId); - - event AssetAddedToTokens( - uint256[] tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetAccepted( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetRejected(uint256 indexed tokenId, uint64 indexed assetId); - - event AssetPrioritySet(uint256 indexed tokenId); - - event ApprovalForAssets( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - event ApprovalForAllForAssets( - address indexed owner, - address indexed operator, - bool approved - ); - - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAllAssets(uint256 tokenId, uint256 maxRejections) external; - - function setPriority( - uint256 tokenId, - uint16[] calldata priorities - ) external; - - function getActiveAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getPendingAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getActiveAssetPriorities( - uint256 tokenId - ) external view returns (uint16[] memory); - - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) external view returns (uint64); - - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) external view returns (string memory); - - function approveForAssets(address to, uint256 tokenId) external; - - function getApprovedForAssets( - uint256 tokenId - ) external view returns (address); - - function setApprovalForAllForAssets( - address operator, - bool approved - ) external; - - function isApprovedForAllForAssets( - address owner, - address operator - ) external view returns (bool); -} diff --git a/assets/eip-6220/contracts/IERC6059.sol b/assets/eip-6220/contracts/IERC6059.sol deleted file mode 100644 index dbad896..0000000 --- a/assets/eip-6220/contracts/IERC6059.sol +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -interface IERC6059 { - struct DirectOwner { - uint256 tokenId; - address ownerAddress; - bool isNft; - } - - event NestTransfer( - address indexed from, - address indexed to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 indexed tokenId - ); - - event ChildProposed( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event ChildAccepted( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event AllChildrenRejected(uint256 indexed tokenId); - - event ChildTransferred( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId, - bool fromPending - ); - - struct Child { - uint256 tokenId; - address contractAddress; - } - - function ownerOf(uint256 tokenId) external view returns (address owner); - - function directOwnerOf( - uint256 tokenId - ) external view returns (address, uint256, bool); - - function burn( - uint256 tokenId, - uint256 maxRecursiveBurns - ) external returns (uint256); - - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) external; - - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) external; - - function rejectAllChildren( - uint256 parentId, - uint256 maxRejections - ) external; - - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) external; - - function childrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function pendingChildrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function childOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function pendingChildOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) external; -} diff --git a/assets/eip-6220/contracts/IERC6220.sol b/assets/eip-6220/contracts/IERC6220.sol deleted file mode 100644 index 859fb74..0000000 --- a/assets/eip-6220/contracts/IERC6220.sol +++ /dev/null @@ -1,194 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "./IERC5773.sol"; - -/** - * @title IERC6220 - * @author RMRK team - * @notice Interface smart contract of the equippable module. - */ -interface IERC6220 is IERC5773 { - /** - * @notice Used to store the core structure of the `Equippable` component. - * @return assetId The ID of the asset equipping a child - * @return childAssetId The ID of the asset used as equipment - * @return childId The ID of token that is equipped - * @return childEquippableAddress Address of the collection to which the child asset belongs to - */ - struct Equipment { - uint64 assetId; - uint64 childAssetId; - uint256 childId; - address childEquippableAddress; - } - - /** - * @notice Used to provide a struct for inputing equip data. - * @dev Only used for input and not storage of data. - * @return tokenId ID of the token we are managing - * @return childIndex Index of a child in the list of token's active children - * @return assetId ID of the asset that we are equipping into - * @return slotPartId ID of the slot part that we are using to equip - * @return childAssetId ID of the asset that we are equipping - */ - struct IntakeEquip { - uint256 tokenId; - uint256 childIndex; - uint64 assetId; - uint64 slotPartId; - uint64 childAssetId; - } - - /** - * @notice Used to notify listeners that a child's asset has been equipped into one of its parent assets. - * @param tokenId ID of the token that had an asset equipped - * @param assetId ID of the asset associated with the token we are equipping into - * @param slotPartId ID of the slot we are using to equip - * @param childId ID of the child token we are equipping into the slot - * @param childAddress Address of the child token's collection - * @param childAssetId ID of the asset associated with the token we are equipping - */ - event ChildAssetEquipped( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed slotPartId, - uint256 childId, - address childAddress, - uint64 childAssetId - ); - - /** - * @notice Used to notify listeners that a child's asset has been unequipped from one of its parent assets. - * @param tokenId ID of the token that had an asset unequipped - * @param assetId ID of the asset associated with the token we are unequipping out of - * @param slotPartId ID of the slot we are unequipping from - * @param childId ID of the token being unequipped - * @param childAddress Address of the collection that a token that is being unequipped belongs to - * @param childAssetId ID of the asset associated with the token we are unequipping - */ - event ChildAssetUnequipped( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed slotPartId, - uint256 childId, - address childAddress, - uint64 childAssetId - ); - - /** - * @notice Used to notify listeners that the assets belonging to a `equippableGroupId` have been marked as - * equippable into a given slot and parent - * @param equippableGroupId ID of the equippable group being marked as equippable into the slot associated with - * `slotPartId` of the `parentAddress` collection - * @param slotPartId ID of the slot part of the catalog into which the parts belonging to the equippable group - * associated with `equippableGroupId` can be equipped - * @param parentAddress Address of the collection into which the parts belonging to `equippableGroupId` can be - * equipped - */ - event ValidParentEquippableGroupIdSet( - uint64 indexed equippableGroupId, - uint64 indexed slotPartId, - address parentAddress - ); - - /** - * @notice Used to equip a child into a token. - * @dev The `IntakeEquip` stuct contains the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data An `IntakeEquip` struct specifying the equip data - */ - function equip( - IntakeEquip memory data - ) external; - - /** - * @notice Used to unequip child from parent token. - * @dev This can only be called by the owner of the token or by an account that has been granted permission to - * manage the given token by the current owner. - * @param tokenId ID of the parent from which the child is being unequipped - * @param assetId ID of the parent's asset that contains the `Slot` into which the child is equipped - * @param slotPartId ID of the `Slot` from which to unequip the child - */ - function unequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) external; - - /** - * @notice Used to check whether the token has a given child equipped. - * @dev This is used to prevent from transferring a child that is equipped. - * @param tokenId ID of the parent token for which we are querying for - * @param childAddress Address of the child token's smart contract - * @param childId ID of the child token - * @return bool The boolean value indicating whether the child token is equipped into the given token or not - */ - function isChildEquipped( - uint256 tokenId, - address childAddress, - uint256 childId - ) external view returns (bool); - - /** - * @notice Used to verify whether a token can be equipped into a given parent's slot. - * @param parent Address of the parent token's smart contract - * @param tokenId ID of the token we want to equip - * @param assetId ID of the asset associated with the token we want to equip - * @param slotId ID of the slot that we want to equip the token into - * @return bool The boolean indicating whether the token with the given asset can be equipped into the desired - * slot - */ - function canTokenBeEquippedWithAssetIntoSlot( - address parent, - uint256 tokenId, - uint64 assetId, - uint64 slotId - ) external view returns (bool); - - /** - * @notice Used to get the Equipment object equipped into the specified slot of the desired token. - * @dev The `Equipment` struct consists of the following data: - * [ - * assetId, - * childAssetId, - * childId, - * childEquippableAddress - * ] - * @param tokenId ID of the token for which we are retrieving the equipped object - * @param targetCatalogAddress Address of the `Catalog` associated with the `Slot` part of the token - * @param slotPartId ID of the `Slot` part that we are checking for equipped objects - * @return struct The `Equipment` struct containing data about the equipped object - */ - function getEquipment( - uint256 tokenId, - address targetCatalogAddress, - uint64 slotPartId - ) external view returns (Equipment memory); - - /** - * @notice Used to get the asset and equippable data associated with given `assetId`. - * @param tokenId ID of the token for which to retrieve the asset - * @param assetId ID of the asset of which we are retrieving - * @return metadataURI The metadata URI of the asset - * @return equippableGroupId ID of the equippable group this asset belongs to - * @return catalogAddress The address of the catalog the part belongs to - * @return partIds An array of IDs of parts included in the asset - */ - function getAssetAndEquippableData(uint256 tokenId, uint64 assetId) - external - view - returns ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - uint64[] calldata partIds - ); -} diff --git a/assets/eip-6220/contracts/library/EquippableLib.sol b/assets/eip-6220/contracts/library/EquippableLib.sol deleted file mode 100644 index 1ae233a..0000000 --- a/assets/eip-6220/contracts/library/EquippableLib.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -library EquippableLib { - function indexOf( - uint64[] memory A, - uint64 a - ) internal pure returns (uint256, bool) { - uint256 length = A.length; - for (uint256 i; i < length; ) { - if (A[i] == a) { - return (i, true); - } - unchecked { - ++i; - } - } - return (0, false); - } - - //For reasource storage array - function removeItemByIndex(uint64[] storage array, uint256 index) internal { - //Check to see if this is already gated by require in all calls - require(index < array.length); - array[index] = array[array.length - 1]; - array.pop(); - } -} diff --git a/assets/eip-6220/contracts/mocks/CatalogMock.sol b/assets/eip-6220/contracts/mocks/CatalogMock.sol deleted file mode 100644 index 106762a..0000000 --- a/assets/eip-6220/contracts/mocks/CatalogMock.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../Catalog.sol"; - -contract CatalogMock is Catalog { - constructor(string memory metadataURI, string memory type_) - Catalog(metadataURI, type_) - {} - - function addPart(IntakeStruct calldata intakeStruct) external { - _addPart(intakeStruct); - } - - function addPartList(IntakeStruct[] calldata intakeStructs) external { - _addPartList(intakeStructs); - } - - function addEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) external { - _addEquippableAddresses(partId, equippableAddresses); - } - - function setEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) external { - _setEquippableAddresses(partId, equippableAddresses); - } - - function setEquippableToAll(uint64 partId) external { - _setEquippableToAll(partId); - } - - function resetEquippableAddresses(uint64 partId) external { - _resetEquippableAddresses(partId); - } -} diff --git a/assets/eip-6220/contracts/mocks/ERC721Mock.sol b/assets/eip-6220/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index 9958688..0000000 --- a/assets/eip-6220/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests with non ERC721 implementer - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} -} diff --git a/assets/eip-6220/contracts/mocks/ERC721ReceiverMock.sol b/assets/eip-6220/contracts/mocks/ERC721ReceiverMock.sol deleted file mode 100644 index 1f0665b..0000000 --- a/assets/eip-6220/contracts/mocks/ERC721ReceiverMock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract ERC721ReceiverMock { - bytes4 constant ERC721_RECEIVED = 0x150b7a02; - - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) public returns (bytes4) { - return ERC721_RECEIVED; - } -} diff --git a/assets/eip-6220/contracts/mocks/EquippableTokenMock.sol b/assets/eip-6220/contracts/mocks/EquippableTokenMock.sol deleted file mode 100644 index 243be83..0000000 --- a/assets/eip-6220/contracts/mocks/EquippableTokenMock.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../EquippableToken.sol"; - -//Minimal public implementation of INestable for testing. -contract EquippableTokenMock is EquippableToken { - address private _issuer; - - constructor() EquippableToken() { - _setIssuer(_msgSender()); - } - - modifier onlyIssuer() { - require(_msgSender() == _issuer, "RMRK: Only issuer"); - _; - } - - function setIssuer(address issuer) external onlyIssuer { - _setIssuer(issuer); - } - - function _setIssuer(address issuer) private { - _issuer = issuer; - } - - function getIssuer() external view returns (address) { - return _issuer; - } - - function mint(address to, uint256 tokenId) external onlyIssuer { - _mint(to, tokenId); - } - - function nestMint( - address to, - uint256 tokenId, - uint256 destinationId - ) external { - _nestMint(to, tokenId, destinationId, ""); - } - - // Utility transfers: - - function transfer(address to, uint256 tokenId) public virtual { - transferFrom(_msgSender(), to, tokenId); - } - - function nestTransfer( - address to, - uint256 tokenId, - uint256 destinationId - ) public virtual { - nestTransferFrom(_msgSender(), to, tokenId, destinationId, ""); - } - - function addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) external onlyIssuer { - _addAssetToToken(tokenId, assetId, replacesAssetWithId); - } - - function addAssetEntry( - uint64 id, - string memory metadataURI - ) external onlyIssuer { - _addAssetEntry(id, metadataURI); - } - - function addEquippableAssetEntry( - uint64 id, - uint64 equippableGroupId, - address catalogAddress, - string memory metadataURI, - uint64[] calldata partIds - ) external onlyIssuer { - _addAssetEntry( - id, - equippableGroupId, - catalogAddress, - metadataURI, - partIds - ); - } - - function setValidParentForEquippableGroup( - uint64 equippableGroupId, - address parentAddress, - uint64 partId - ) external { - _setValidParentForEquippableGroup( - equippableGroupId, - parentAddress, - partId - ); - } -} diff --git a/assets/eip-6220/contracts/mocks/NonReceiverMock.sol b/assets/eip-6220/contracts/mocks/NonReceiverMock.sol deleted file mode 100644 index e9d2d6e..0000000 --- a/assets/eip-6220/contracts/mocks/NonReceiverMock.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract NonReceiverMock { - function dummy() external {} -} diff --git a/assets/eip-6220/contracts/utils/EquipRenderUtils.sol b/assets/eip-6220/contracts/utils/EquipRenderUtils.sol deleted file mode 100644 index 6637d5a..0000000 --- a/assets/eip-6220/contracts/utils/EquipRenderUtils.sol +++ /dev/null @@ -1,481 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../ICatalog.sol"; -import "../IEquippable.sol"; -import "../library/EquippableLib.sol"; - -error TokenHasNoAssets(); -error NotComposableAsset(); - -/** - * @title EquipRenderUtils - * @author RMRK team - * @notice Smart contract of the RMRK Equip render utils module. - * @dev Extra utility functions for composing RMRK extended assets. - */ -contract EquipRenderUtils { - using EquippableLib for uint64[]; - - /** - * @notice The structure used to display a full information of an active asset. - * @return id ID of the asset - * @return equppableGroupId ID of the equippable group this asset belongs to - * @return priority Priority of the asset in the active assets array it belongs to - * @return catalogAddress Address of the `Catalog` smart contract this asset belongs to - * @return metadata Metadata URI of the asset - * @return partIds[] An array of IDs of fixed and slot parts present in the asset - */ - struct ExtendedActiveAsset { - uint64 id; - uint64 equippableGroupId; - uint16 priority; - address catalogAddress; - string metadata; - uint64[] partIds; - } - - /** - * @notice The structure used to display a full information of a pending asset. - * @return id ID of the asset - * @return equppableGroupId ID of the equippable group this asset belongs to - * @return acceptRejectIndex The index of the given asset in the pending assets array it belongs to - * @return replacesAssetWithId ID of the asset the given asset will replace if accepted - * @return catalogAddress Address of the `Catalog` smart contract this asset belongs to - * @return metadata Metadata URI of the asset - * @return partIds[] An array of IDs of fixed and slot parts present in the asset - */ - struct ExtendedPendingAsset { - uint64 id; - uint64 equippableGroupId; - uint128 acceptRejectIndex; - uint64 replacesAssetWithId; - address catalogAddress; - string metadata; - uint64[] partIds; - } - - /** - * @notice The structure used to display a full information of an equippend slot part. - * @return partId ID of the slot part - * @return childAssetId ID of the child asset equipped into the slot part - * @return z The z value of the part defining how it should be rendered when presenting the full NFT - * @return childAddress Address of the collection smart contract of the child token equipped into the slot - * @return childId ID of the child token equipped into the slot - * @return childAssetMetadata Metadata URI of the child token equipped into the slot - * @return partMetadata Metadata URI of the given slot part - */ - struct EquippedSlotPart { - uint64 partId; - uint64 childAssetId; - uint8 z; //1 byte - address childAddress; - uint256 childId; - string childAssetMetadata; //n bytes 32+ - string partMetadata; //n bytes 32+ - } - - /** - * @notice Used to provide data about fixed parts. - * @return partId ID of the part - * @return z The z value of the asset, specifying how the part should be rendered in a composed NFT - * @return matadataURI The metadata URI of the fixed part - */ - struct FixedPart { - uint64 partId; - uint8 z; //1 byte - string metadataURI; //n bytes 32+ - } - - /** - * @notice Used to get extended active assets of the given token. - * @dev The full `ExtendedActiveAsset` looks like this: - * [ - * ID, - * equippableGroupId, - * priority, - * catalogAddress, - * metadata, - * [ - * fixedPartId0, - * fixedPartId1, - * fixedPartId2, - * slotPartId0, - * slotPartId1, - * slotPartId2 - * ] - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the extended active assets for - * @return sturct[] An array of ExtendedActiveAssets present on the given token - */ - function getExtendedActiveAssets(address target, uint256 tokenId) - public - view - virtual - returns (ExtendedActiveAsset[] memory) - { - IEquippable target_ = IEquippable(target); - - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint16[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - ExtendedActiveAsset[] memory activeAssets = new ExtendedActiveAsset[]( - len - ); - - for (uint256 i; i < len; ) { - ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - uint64[] memory partIds - ) = target_.getAssetAndEquippableData(tokenId, assets[i]); - activeAssets[i] = ExtendedActiveAsset({ - id: assets[i], - equippableGroupId: equippableGroupId, - priority: priorities[i], - catalogAddress: catalogAddress, - metadata: metadataURI, - partIds: partIds - }); - unchecked { - ++i; - } - } - return activeAssets; - } - - /** - * @notice Used to get the extended pending assets of the given token. - * @dev The full `ExtendedPendingAsset` looks like this: - * [ - * ID, - * equippableGroupId, - * acceptRejectIndex, - * replacesAssetWithId, - * catalogAddress, - * metadata, - * [ - * fixedPartId0, - * fixedPartId1, - * fixedPartId2, - * slotPartId0, - * slotPartId1, - * slotPartId2 - * ] - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the extended pending assets for - * @return sturct[] An array of ExtendedPendingAssets present on the given token - */ - function getExtendedPendingAssets(address target, uint256 tokenId) - public - view - virtual - returns (ExtendedPendingAsset[] memory) - { - IEquippable target_ = IEquippable(target); - - uint64[] memory assets = target_.getPendingAssets(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - ExtendedPendingAsset[] - memory pendingAssets = new ExtendedPendingAsset[](len); - uint64 replacesAssetWithId; - for (uint256 i; i < len; ) { - ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - uint64[] memory partIds - ) = target_.getAssetAndEquippableData(tokenId, assets[i]); - replacesAssetWithId = target_.getAssetReplacements( - tokenId, - assets[i] - ); - pendingAssets[i] = ExtendedPendingAsset({ - id: assets[i], - equippableGroupId: equippableGroupId, - acceptRejectIndex: uint128(i), - replacesAssetWithId: replacesAssetWithId, - catalogAddress: catalogAddress, - metadata: metadataURI, - partIds: partIds - }); - unchecked { - ++i; - } - } - return pendingAssets; - } - - /** - * @notice Used to retrieve the equipped parts of the given token. - * @dev NOTE: Some of the equipped children might be empty. - * @dev The full `Equipment` struct looks like this: - * [ - * assetId, - * childAssetId, - * childId, - * childEquippableAddress - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the equipped items in the asset for - * @param assetId ID of the asset being queried for equipped parts - * @return slotPartIds An array of the IDs of the slot parts present in the given asset - * @return childrenEquipped An array of `Equipment` structs containing info about the equipped children - */ - function getEquipped( - address target, - uint64 tokenId, - uint64 assetId - ) - public - view - returns ( - uint64[] memory slotPartIds, - IEquippable.Equipment[] memory childrenEquipped - ) - { - IEquippable target_ = IEquippable(target); - - (, , address catalogAddress, uint64[] memory partIds) = target_ - .getAssetAndEquippableData(tokenId, assetId); - - (slotPartIds, ) = splitSlotAndFixedParts(partIds, catalogAddress); - childrenEquipped = new IEquippable.Equipment[](slotPartIds.length); - - uint256 len = slotPartIds.length; - for (uint256 i; i < len; ) { - IEquippable.Equipment memory equipment = target_.getEquipment( - tokenId, - catalogAddress, - slotPartIds[i] - ); - if (equipment.assetId == assetId) { - childrenEquipped[i] = equipment; - } - unchecked { - ++i; - } - } - } - - /** - * @notice Used to compose the given equippables. - * @dev The full `FixedPart` struct looks like this: - * [ - * partId, - * z, - * metadataURI - * ] - * @dev The full `EquippedSlotPart` struct looks like this: - * [ - * partId, - * childAssetId, - * z, - * childAddress, - * childId, - * childAssetMetadata, - * partMetadata - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to compose the equipped items in the asset for - * @param assetId ID of the asset being queried for equipped parts - * @return metadataURI Metadata URI of the asset - * @return equippableGroupId Equippable group ID of the asset - * @return catalogAddress Address of the catalog to which the asset belongs to - * @return fixedParts An array of fixed parts respresented by the `FixedPart` structs present on the asset - * @return slotParts An array of slot parts represented by the `EquippedSlotPart` structs present on the asset - */ - function composeEquippables( - address target, - uint256 tokenId, - uint64 assetId - ) - public - view - returns ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - FixedPart[] memory fixedParts, - EquippedSlotPart[] memory slotParts - ) - { - IEquippable target_ = IEquippable(target); - uint64[] memory partIds; - - // If token does not have uint64[] memory slotPartId to save the asset, it would fail here. - (metadataURI, equippableGroupId, catalogAddress, partIds) = target_ - .getAssetAndEquippableData(tokenId, assetId); - if (catalogAddress == address(0)) revert NotComposableAsset(); - - ( - uint64[] memory slotPartIds, - uint64[] memory fixedPartIds - ) = splitSlotAndFixedParts(partIds, catalogAddress); - - // Fixed parts: - fixedParts = new FixedPart[](fixedPartIds.length); - - uint256 len = fixedPartIds.length; - if (len != 0) { - ICatalog.Part[] memory catalogFixedParts = ICatalog( - catalogAddress - ).getParts(fixedPartIds); - for (uint256 i; i < len; ) { - fixedParts[i] = FixedPart({ - partId: fixedPartIds[i], - z: catalogFixedParts[i].z, - metadataURI: catalogFixedParts[i].metadataURI - }); - unchecked { - ++i; - } - } - } - - slotParts = getEquippedSlotParts( - target_, - tokenId, - assetId, - catalogAddress, - slotPartIds - ); - } - - /** - * @notice Used to retrieve the equipped slot parts. - * @dev The full `EquippedSlotPart` struct looks like this: - * [ - * partId, - * childAssetId, - * z, - * childAddress, - * childId, - * childAssetMetadata, - * partMetadata - * ] - * @param target_ An address of the `IEquippable` smart contract to retrieve the equipped slot parts from. - * @param tokenId ID of the token for which to retrieve the equipped slot parts - * @param assetId ID of the asset on the token to retrieve the equipped slot parts - * @param catalogAddress The address of the catalog to which the given asset belongs to - * @param slotPartIds An array of slot part IDs in the asset for which to retrieve the equipped slot parts - * @return slotParts An array of `EquippedSlotPart` structs representing the equipped slot parts - */ - function getEquippedSlotParts( - IEquippable target_, - uint256 tokenId, - uint64 assetId, - address catalogAddress, - uint64[] memory slotPartIds - ) private view returns (EquippedSlotPart[] memory slotParts) { - slotParts = new EquippedSlotPart[](slotPartIds.length); - uint256 len = slotPartIds.length; - - if (len != 0) { - string memory metadata; - ICatalog.Part[] memory catalogSlotParts = ICatalog(catalogAddress) - .getParts(slotPartIds); - for (uint256 i; i < len; ) { - IEquippable.Equipment memory equipment = target_.getEquipment( - tokenId, - catalogAddress, - slotPartIds[i] - ); - if (equipment.assetId == assetId) { - metadata = IEquippable(equipment.childEquippableAddress) - .getAssetMetadata( - equipment.childId, - equipment.childAssetId - ); - slotParts[i] = EquippedSlotPart({ - partId: slotPartIds[i], - childAssetId: equipment.childAssetId, - z: catalogSlotParts[i].z, - childId: equipment.childId, - childAddress: equipment.childEquippableAddress, - childAssetMetadata: metadata, - partMetadata: catalogSlotParts[i].metadataURI - }); - } else { - slotParts[i] = EquippedSlotPart({ - partId: slotPartIds[i], - childAssetId: uint64(0), - z: catalogSlotParts[i].z, - childId: uint256(0), - childAddress: address(0), - childAssetMetadata: "", - partMetadata: catalogSlotParts[i].metadataURI - }); - } - unchecked { - ++i; - } - } - } - } - - /** - * @notice Used to split slot and fixed parts. - * @param allPartIds[] An array of `Part` IDs containing both, `Slot` and `Fixed` parts - * @param catalogAddress An address of the catalog to which the given `Part`s belong to - * @return slotPartIds An array of IDs of the `Slot` parts included in the `allPartIds` - * @return fixedPartIds An array of IDs of the `Fixed` parts included in the `allPartIds` - */ - function splitSlotAndFixedParts( - uint64[] memory allPartIds, - address catalogAddress - ) - public - view - returns (uint64[] memory slotPartIds, uint64[] memory fixedPartIds) - { - ICatalog.Part[] memory allParts = ICatalog(catalogAddress) - .getParts(allPartIds); - uint256 numFixedParts; - uint256 numSlotParts; - - uint256 numParts = allPartIds.length; - // This for loop is just to discover the right size of the split arrays, since we can't create them dynamically - for (uint256 i; i < numParts; ) { - if (allParts[i].itemType == ICatalog.ItemType.Fixed) - numFixedParts += 1; - // We could just take the numParts - numFixedParts, but it doesn't hurt to double check it's not an uninitialized part: - else if (allParts[i].itemType == ICatalog.ItemType.Slot) - numSlotParts += 1; - unchecked { - ++i; - } - } - - slotPartIds = new uint64[](numSlotParts); - fixedPartIds = new uint64[](numFixedParts); - uint256 slotPartsIndex; - uint256 fixedPartsIndex; - - // This for loop is to actually fill the split arrays - for (uint256 i; i < numParts; ) { - if (allParts[i].itemType == ICatalog.ItemType.Fixed) { - fixedPartIds[fixedPartsIndex] = allPartIds[i]; - fixedPartsIndex += 1; - } else if (allParts[i].itemType == ICatalog.ItemType.Slot) { - slotPartIds[slotPartsIndex] = allPartIds[i]; - slotPartsIndex += 1; - } - unchecked { - ++i; - } - } - } -} diff --git a/assets/eip-6220/contracts/utils/MultiAssetRenderUtils.sol b/assets/eip-6220/contracts/utils/MultiAssetRenderUtils.sol deleted file mode 100644 index 47d8dcb..0000000 --- a/assets/eip-6220/contracts/utils/MultiAssetRenderUtils.sol +++ /dev/null @@ -1,194 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../IERC5773.sol"; - -error TokenHasNoAssets(); - -/** - * @title MultiAssetRenderUtils - * @author RMRK team - */ -contract MultiAssetRenderUtils { - uint16 private constant _LOWEST_POSSIBLE_PRIORITY = 2**16 - 1; - - /** - * @notice The structure used to display information about an active asset. - * @return id ID of the asset - * @return priority The priority assigned to the asset - * @return metadata The metadata URI of the asset - */ - struct ActiveAsset { - uint64 id; - uint16 priority; - string metadata; - } - - /** - * @notice The structure used to display information about a pending asset. - * @return id ID of the asset - * @return acceptRejectIndex An index to use in order to accept or reject the given asset - * @return replacesAssetWithId ID of the asset that would be replaced if this asset gets accepted - * @return metadata The metadata URI of the asset - */ - struct PendingAsset { - uint64 id; - uint128 acceptRejectIndex; - uint64 replacesAssetWithId; - string metadata; - } - - /** - * @notice Used to get the active assets of the given token. - * @dev The full `ActiveAsset` looks like this: - * [ - * id, - * priority, - * metadata - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the active assets for - * @return struct[] An array of ActiveAssets present on the given token - */ - function getActiveAssets(address target, uint256 tokenId) - public - view - virtual - returns (ActiveAsset[] memory) - { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint16[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - ActiveAsset[] memory activeAssets = new ActiveAsset[](len); - string memory metadata; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - activeAssets[i] = ActiveAsset({ - id: assets[i], - priority: priorities[i], - metadata: metadata - }); - unchecked { - ++i; - } - } - return activeAssets; - } - - /** - * @notice Used to get the pending assets of the given token. - * @dev The full `PendingAsset` looks like this: - * [ - * id, - * acceptRejectIndex, - * replacesAssetWithId, - * metadata - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the pending assets for - * @return struct[] An array of PendingAssets present on the given token - */ - function getPendingAssets(address target, uint256 tokenId) - public - view - virtual - returns (PendingAsset[] memory) - { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getPendingAssets(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - PendingAsset[] memory pendingAssets = new PendingAsset[](len); - string memory metadata; - uint64 replacesAssetWithId; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - replacesAssetWithId = target_.getAssetReplacements( - tokenId, - assets[i] - ); - pendingAssets[i] = PendingAsset({ - id: assets[i], - acceptRejectIndex: uint128(i), - replacesAssetWithId: replacesAssetWithId, - metadata: metadata - }); - unchecked { - ++i; - } - } - return pendingAssets; - } - - /** - * @notice Used to retrieve the metadata URI of specified assets in the specified token. - * @dev Requirements: - * - * - `assetIds` must exist. - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the specified assets for - * @param assetIds[] An array of asset IDs for which to retrieve the metadata URIs - * @return string[] An array of metadata URIs belonging to specified assets - */ - function getAssetsById( - address target, - uint256 tokenId, - uint64[] calldata assetIds - ) public view virtual returns (string[] memory) { - IERC5773 target_ = IERC5773(target); - uint256 len = assetIds.length; - string[] memory assets = new string[](len); - for (uint256 i; i < len; ) { - assets[i] = target_.getAssetMetadata(tokenId, assetIds[i]); - unchecked { - ++i; - } - } - return assets; - } - - /** - * @notice Used to retrieve the metadata URI of the specified token's asset with the highest priority. - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token for which to retrieve the metadata URI of the asset with the highest priority - * @return string The metadata URI of the asset with the highest priority - */ - function getTopAssetMetaForToken(address target, uint256 tokenId) - external - view - returns (string memory) - { - IERC5773 target_ = IERC5773(target); - uint16[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint256 len = priorities.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - uint16 maxPriority = _LOWEST_POSSIBLE_PRIORITY; - uint64 maxPriorityAsset; - for (uint64 i; i < len; ) { - uint16 currentPrio = priorities[i]; - if (currentPrio < maxPriority) { - maxPriority = currentPrio; - maxPriorityAsset = assets[i]; - } - unchecked { - ++i; - } - } - return target_.getAssetMetadata(tokenId, maxPriorityAsset); - } -} diff --git a/assets/eip-6220/hardhat.config.ts b/assets/eip-6220/hardhat.config.ts deleted file mode 100644 index b00785f..0000000 --- a/assets/eip-6220/hardhat.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@nomiclabs/hardhat-etherscan"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.16", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6220/package.json b/assets/eip-6220/package.json deleted file mode 100644 index 3adc2cd..0000000 --- a/assets/eip-6220/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "erc-6220", - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/test-helpers": "^0.5.15", - "@primitivefi/hardhat-dodoc": "^0.2.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.3", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "solc": "^0.8.9", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "walk-sync": "^3.0.0" - } -} diff --git a/assets/eip-6220/test/catalog.ts b/assets/eip-6220/test/catalog.ts deleted file mode 100644 index a9608d4..0000000 --- a/assets/eip-6220/test/catalog.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { CatalogMock } from "../typechain-types"; - -async function catalogFixture(): Promise { - const Catalog = await ethers.getContractFactory("CatalogMock"); - const testCatalog = await Catalog.deploy("ipfs//:meta", "misc"); - await testCatalog.deployed(); - - return testCatalog; -} - -describe("CatalogMock", async () => { - let testCatalog: CatalogMock; - - let addrs: SignerWithAddress[]; - const metadataUriDefault = "src"; - - const noType = 0; - const slotType = 1; - const fixedType = 2; - - const sampleSlotPartData = { - itemType: slotType, - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - - beforeEach(async () => { - [, ...addrs] = await ethers.getSigners(); - testCatalog = await loadFixture(catalogFixture); - }); - - describe("Init Catalog", async function () { - it("has right metadataURI", async function () { - expect(await testCatalog.getMetadataURI()).to.equal("ipfs//:meta"); - }); - - it("has right type", async function () { - expect(await testCatalog.getType()).to.equal("misc"); - }); - - it("supports interface", async function () { - expect(await testCatalog.supportsInterface("0xd912401f")).to.equal(true); - }); - - it("does not support other interfaces", async function () { - expect(await testCatalog.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("add catalog entries", async function () { - it("can add fixed part", async function () { - const partId = 1; - const partData = { - itemType: fixedType, - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - - await testCatalog.addPart({ partId: partId, part: partData }); - expect(await testCatalog.getPart(partId)).to.eql([ - 2, - 0, - [], - metadataUriDefault, - ]); - }); - - it("can add slot part", async function () { - const partId = 2; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - expect(await testCatalog.getPart(partId)).to.eql([ - 1, - 0, - [], - metadataUriDefault, - ]); - }); - - it("can add parts list", async function () { - const partId = 1; - const partId2 = 2; - const partData1 = { - itemType: slotType, - z: 0, - equippable: [], - metadataURI: "src1", - }; - const partData2 = { - itemType: fixedType, - z: 1, - equippable: [], - metadataURI: "src2", - }; - await testCatalog.addPartList([ - { partId: partId, part: partData1 }, - { partId: partId2, part: partData2 }, - ]); - expect(await testCatalog.getParts([partId, partId2])).to.eql([ - [slotType, 0, [], "src1"], - [fixedType, 1, [], "src2"], - ]); - }); - - it("cannot add part with id 0", async function () { - const partId = 0; - await expect( - testCatalog.addPart({ partId: partId, part: sampleSlotPartData }) - ).to.be.revertedWithCustomError(testCatalog, "IdZeroForbidden"); - }); - - it("cannot add part with existing partId", async function () { - const partId = 3; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect( - testCatalog.addPart({ partId: partId, part: sampleSlotPartData }) - ).to.be.revertedWithCustomError(testCatalog, "PartAlreadyExists"); - }); - - it("cannot add part with item type None", async function () { - const partId = 1; - const badPartData = { - itemType: noType, - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - await expect( - testCatalog.addPart({ partId: partId, part: badPartData }) - ).to.be.revertedWithCustomError(testCatalog, "BadConfig"); - }); - - it("cannot add fixed part with equippable addresses", async function () { - const partId = 1; - const badPartData = { - itemType: fixedType, - z: 0, - equippable: [addrs[3].address], - metadataURI: metadataUriDefault, - }; - await expect( - testCatalog.addPart({ partId: partId, part: badPartData }) - ).to.be.revertedWithCustomError(testCatalog, "BadConfig"); - }); - - it("is not equippable if address was not added", async function () { - const partId = 4; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(false); - }); - - it("is equippable if added in the part definition", async function () { - const partId = 1; - const partData = { - itemType: slotType, - z: 0, - equippable: [addrs[1].address, addrs[2].address], - metadataURI: metadataUriDefault, - }; - await testCatalog.addPart({ partId: partId, part: partData }); - expect( - await testCatalog.checkIsEquippable(partId, addrs[2].address) - ).to.eql(true); - }); - - it("is equippable if added afterward", async function () { - const partId = 1; - await expect( - testCatalog.addPart({ partId: partId, part: sampleSlotPartData }) - ) - .to.emit(testCatalog, "AddedPart") - .withArgs( - partId, - sampleSlotPartData.itemType, - sampleSlotPartData.z, - sampleSlotPartData.equippable, - sampleSlotPartData.metadataURI - ); - await expect( - testCatalog.addEquippableAddresses(partId, [addrs[1].address]) - ) - .to.emit(testCatalog, "AddedEquippables") - .withArgs(partId, [addrs[1].address]); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(true); - }); - - it("is equippable if set afterward", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect( - testCatalog.setEquippableAddresses(partId, [addrs[1].address]) - ) - .to.emit(testCatalog, "SetEquippables") - .withArgs(partId, [addrs[1].address]); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(true); - }); - - it("is equippable if set to all", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect(testCatalog.setEquippableToAll(partId)) - .to.emit(testCatalog, "SetEquippableToAll") - .withArgs(partId); - expect(await testCatalog.checkIsEquippableToAll(partId)).to.eql(true); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(true); - }); - - it("cannot add nor set equippable addresses for non existing part", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect( - testCatalog.addEquippableAddresses(partId, []) - ).to.be.revertedWithCustomError(testCatalog, "ZeroLengthIdsPassed"); - await expect( - testCatalog.setEquippableAddresses(partId, []) - ).to.be.revertedWithCustomError(testCatalog, "ZeroLengthIdsPassed"); - }); - - it("cannot add nor set empty list of equippable addresses", async function () { - const NonExistingPartId = 1; - await expect( - testCatalog.addEquippableAddresses(NonExistingPartId, [ - addrs[1].address, - ]) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - await expect( - testCatalog.setEquippableAddresses(NonExistingPartId, [ - addrs[1].address, - ]) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - await expect( - testCatalog.setEquippableToAll(NonExistingPartId) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - }); - - it("cannot add nor set equippable addresses to non slot part", async function () { - const fixedPartId = 1; - const partData = { - itemType: fixedType, // This is what we're testing - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - await testCatalog.addPart({ partId: fixedPartId, part: partData }); - await expect( - testCatalog.addEquippableAddresses(fixedPartId, [addrs[1].address]) - ).to.be.revertedWithCustomError(testCatalog, "PartIsNotSlot"); - await expect( - testCatalog.setEquippableAddresses(fixedPartId, [addrs[1].address]) - ).to.be.revertedWithCustomError(testCatalog, "PartIsNotSlot"); - await expect( - testCatalog.setEquippableToAll(fixedPartId) - ).to.be.revertedWithCustomError(testCatalog, "PartIsNotSlot"); - }); - - it("cannot set equippable to all on non existing part", async function () { - const nonExistingPartId = 1; - await expect( - testCatalog.setEquippableToAll(nonExistingPartId) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - }); - - it("resets equippable to all if addresses are set", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await testCatalog.setEquippableToAll(partId); - - // This should reset it: - testCatalog.setEquippableAddresses(partId, [addrs[1].address]); - expect(await testCatalog.checkIsEquippableToAll(partId)).to.eql(false); - }); - - it("resets equippable to all if addresses are added", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await testCatalog.setEquippableToAll(partId); - - // This should reset it: - await testCatalog.addEquippableAddresses(partId, [addrs[1].address]); - expect(await testCatalog.checkIsEquippableToAll(partId)).to.eql(false); - }); - - it("can reset equippable addresses", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await testCatalog.addEquippableAddresses(partId, [ - addrs[1].address, - addrs[2].address, - ]); - - await testCatalog.resetEquippableAddresses(partId); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(false); - }); - }); -}); diff --git a/assets/eip-6220/test/equippableFixedParts.ts b/assets/eip-6220/test/equippableFixedParts.ts deleted file mode 100644 index 78c1c39..0000000 --- a/assets/eip-6220/test/equippableFixedParts.ts +++ /dev/null @@ -1,662 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { - CatalogMock, - EquippableTokenMock, - EquipRenderUtils, -} from "../typechain-types"; - -let addrs: SignerWithAddress[]; - -const partIdForHead1 = 1; -const partIdForHead2 = 2; -const partIdForHead3 = 3; -const partIdForBody1 = 4; -const partIdForBody2 = 5; -const partIdForHair1 = 6; -const partIdForHair2 = 7; -const partIdForHair3 = 8; -const partIdForMaskCatalog1 = 9; -const partIdForMaskCatalog2 = 10; -const partIdForMaskCatalog3 = 11; -const partIdForEars1 = 12; -const partIdForEars2 = 13; -const partIdForHorns1 = 14; -const partIdForHorns2 = 15; -const partIdForHorns3 = 16; -const partIdForMaskCatalogEquipped1 = 17; -const partIdForMaskCatalogEquipped2 = 18; -const partIdForMaskCatalogEquipped3 = 19; -const partIdForEarsEquipped1 = 20; -const partIdForEarsEquipped2 = 21; -const partIdForHornsEquipped1 = 22; -const partIdForHornsEquipped2 = 23; -const partIdForHornsEquipped3 = 24; -const partIdForMask = 25; - -const uniqueNeons = 10; -const uniqueMasks = 4; -// Ids could be the same since they are different collections, but to avoid log problems we have them unique -const neons: number[] = []; -const masks: number[] = []; - -const neonResIds = [100, 101, 102, 103, 104]; -const maskAssetsFull = [1, 2, 3, 4]; // Must match the total of uniqueAssets -const maskAssetsEquip = [5, 6, 7, 8]; // Must match the total of uniqueAssets -const maskpableGroupId = 1; // Assets to equip will all use this - -enum ItemType { - None, - Slot, - Fixed, -} - -let nextTokenId = 1; -let nextChildTokenId = 100; - -async function mint(token: EquippableTokenMock, to: string): Promise { - const tokenId = nextTokenId; - nextTokenId++; - await token["mint(address,uint256)"](to, tokenId); - return tokenId; -} - -async function nestMint( - token: EquippableTokenMock, - to: string, - parentId: number -): Promise { - const childTokenId = nextChildTokenId; - nextChildTokenId++; - await token["nestMint(address,uint256,uint256)"](to, childTokenId, parentId); - return childTokenId; -} - -async function setupContextForParts( - catalog: CatalogMock, - neon: EquippableTokenMock, - mask: EquippableTokenMock -) { - [, ...addrs] = await ethers.getSigners(); - await setupCatalog(); - - await mintNeons(); - await mintMasks(); - - await addAssetsToNeon(); - await addAssetsToMask(); - - async function setupCatalog(): Promise { - const partForHead1 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://head1.png", - }; - const partForHead2 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://head2.png", - }; - const partForHead3 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://head3.png", - }; - const partForBody1 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://body1.png", - }; - const partForBody2 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://body2.png", - }; - const partForHair1 = { - itemType: ItemType.Fixed, - z: 2, - equippable: [], - metadataURI: "ipfs://hair1.png", - }; - const partForHair2 = { - itemType: ItemType.Fixed, - z: 2, - equippable: [], - metadataURI: "ipfs://hair2.png", - }; - const partForHair3 = { - itemType: ItemType.Fixed, - z: 2, - equippable: [], - metadataURI: "ipfs://hair3.png", - }; - const partForMaskCatalog1 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalog1.png", - }; - const partForMaskCatalog2 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalog2.png", - }; - const partForMaskCatalog3 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalog3.png", - }; - const partForEars1 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://ears1.png", - }; - const partForEars2 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://ears2.png", - }; - const partForHorns1 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://horn1.png", - }; - const partForHorns2 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://horn2.png", - }; - const partForHorns3 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://horn3.png", - }; - const partForMaskCatalogEquipped1 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalogEquipped1.png", - }; - const partForMaskCatalogEquipped2 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalogEquipped2.png", - }; - const partForMaskCatalogEquipped3 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalogEquipped3.png", - }; - const partForEarsEquipped1 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://earsEquipped1.png", - }; - const partForEarsEquipped2 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://earsEquipped2.png", - }; - const partForHornsEquipped1 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://hornEquipped1.png", - }; - const partForHornsEquipped2 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://hornEquipped2.png", - }; - const partForHornsEquipped3 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://hornEquipped3.png", - }; - const partForMask = { - itemType: ItemType.Slot, - z: 2, - equippable: [mask.address], - metadataURI: "", - }; - - await catalog.addPartList([ - { partId: partIdForHead1, part: partForHead1 }, - { partId: partIdForHead2, part: partForHead2 }, - { partId: partIdForHead3, part: partForHead3 }, - { partId: partIdForBody1, part: partForBody1 }, - { partId: partIdForBody2, part: partForBody2 }, - { partId: partIdForHair1, part: partForHair1 }, - { partId: partIdForHair2, part: partForHair2 }, - { partId: partIdForHair3, part: partForHair3 }, - { partId: partIdForMaskCatalog1, part: partForMaskCatalog1 }, - { partId: partIdForMaskCatalog2, part: partForMaskCatalog2 }, - { partId: partIdForMaskCatalog3, part: partForMaskCatalog3 }, - { partId: partIdForEars1, part: partForEars1 }, - { partId: partIdForEars2, part: partForEars2 }, - { partId: partIdForHorns1, part: partForHorns1 }, - { partId: partIdForHorns2, part: partForHorns2 }, - { partId: partIdForHorns3, part: partForHorns3 }, - { - partId: partIdForMaskCatalogEquipped1, - part: partForMaskCatalogEquipped1, - }, - { - partId: partIdForMaskCatalogEquipped2, - part: partForMaskCatalogEquipped2, - }, - { - partId: partIdForMaskCatalogEquipped3, - part: partForMaskCatalogEquipped3, - }, - { partId: partIdForEarsEquipped1, part: partForEarsEquipped1 }, - { partId: partIdForEarsEquipped2, part: partForEarsEquipped2 }, - { partId: partIdForHornsEquipped1, part: partForHornsEquipped1 }, - { partId: partIdForHornsEquipped2, part: partForHornsEquipped2 }, - { partId: partIdForHornsEquipped3, part: partForHornsEquipped3 }, - { partId: partIdForMask, part: partForMask }, - ]); - } - - async function mintNeons(): Promise { - // This array is reused, so we "empty" it before - neons.length = 0; - // Using only first 3 addresses to mint - for (let i = 0; i < uniqueNeons; i++) { - const newId = await mint(neon, addrs[i % 3].address); - neons.push(newId); - } - } - - async function mintMasks(): Promise { - // This array is reused, so we "empty" it before - masks.length = 0; - // Mint one weapon to neon - for (let i = 0; i < uniqueNeons; i++) { - const newId = await nestMint(mask, neon.address, neons[i]); - masks.push(newId); - await neon - .connect(addrs[i % 3]) - .acceptChild(neons[i], 0, mask.address, newId); - } - } - - async function addAssetsToNeon(): Promise { - await neon.addEquippableAssetEntry( - neonResIds[0], - 0, - catalog.address, - "ipfs:neonRes/1", - [partIdForHead1, partIdForBody1, partIdForHair1, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[1], - 0, - catalog.address, - "ipfs:neonRes/2", - [partIdForHead2, partIdForBody2, partIdForHair2, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[2], - 0, - catalog.address, - "ipfs:neonRes/3", - [partIdForHead3, partIdForBody1, partIdForHair3, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[3], - 0, - catalog.address, - "ipfs:neonRes/4", - [partIdForHead1, partIdForBody2, partIdForHair2, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[4], - 0, - catalog.address, - "ipfs:neonRes/1", - [partIdForHead2, partIdForBody1, partIdForHair1, partIdForMask] - ); - - for (let i = 0; i < uniqueNeons; i++) { - await neon.addAssetToToken( - neons[i], - neonResIds[i % neonResIds.length], - 0 - ); - await neon - .connect(addrs[i % 3]) - .acceptAsset(neons[i], 0, neonResIds[i % neonResIds.length]); - } - } - - async function addAssetsToMask(): Promise { - // Assets for full view, composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsFull[0], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[0]}`, - [partIdForMaskCatalog1, partIdForHorns1, partIdForEars1] - ); - await mask.addEquippableAssetEntry( - maskAssetsFull[1], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[1]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars2] - ); - await mask.addEquippableAssetEntry( - maskAssetsFull[2], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[2]}`, - [partIdForMaskCatalog3, partIdForHorns1, partIdForEars2] - ); - await mask.addEquippableAssetEntry( - maskAssetsFull[3], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[3]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars1] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[0], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[0]}`, - [partIdForMaskCatalog1, partIdForHorns1, partIdForEars1] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[1], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[1]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars2] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[2], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[2]}`, - [partIdForMaskCatalog3, partIdForHorns1, partIdForEars2] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[3], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[3]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars1] - ); - - // Can be equipped into neons - await mask.setValidParentForEquippableGroup( - maskpableGroupId, - neon.address, - partIdForMask - ); - - // Add 2 assets to each weapon, one full, one for equip - // There are 10 weapon tokens for 4 unique assets so we use % - for (let i = 0; i < masks.length; i++) { - await mask.addAssetToToken(masks[i], maskAssetsFull[i % uniqueMasks], 0); - await mask.addAssetToToken(masks[i], maskAssetsEquip[i % uniqueMasks], 0); - await mask - .connect(addrs[i % 3]) - .acceptAsset(masks[i], 0, maskAssetsFull[i % uniqueMasks]); - await mask - .connect(addrs[i % 3]) - .acceptAsset(masks[i], 0, maskAssetsEquip[i % uniqueMasks]); - } - } -} - -async function partsFixture() { - const baseSymbol = "NCB"; - const baseType = "mixed"; - - const baseFactory = await ethers.getContractFactory("CatalogMock"); - const equipFactory = await ethers.getContractFactory("EquippableTokenMock"); - const viewFactory = await ethers.getContractFactory("EquipRenderUtils"); - - // Catalog - const catalog = await baseFactory.deploy(baseSymbol, baseType); - await catalog.deployed(); - - // Neon token - const neon = await equipFactory.deploy(); - await neon.deployed(); - - // Weapon - const mask = await equipFactory.deploy(); - await mask.deployed(); - - // View - const view = await viewFactory.deploy(); - await view.deployed(); - - await setupContextForParts(catalog, neon, mask); - return { catalog, neon, mask, view }; -} - -// The general idea is having these tokens: Neon and Mask -// Masks can be equipped into Neons. -// All use a single catalog. -// Neon will use an asset per token, which uses fixed parts to compose the body -// Mask will have 2 assets per weapon, one for full view, one for equipping. Both are composed using fixed parts -describe("EquippableTokenMock with Parts", async () => { - let catalog: CatalogMock; - let neon: EquippableTokenMock; - let mask: EquippableTokenMock; - let view: EquipRenderUtils; - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [, ...addrs] = await ethers.getSigners(); - ({ catalog, neon, mask, view } = await loadFixture(partsFixture)); - }); - - describe("Equip", async function () { - it("can equip weapon", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = maskAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - neon - .connect(addrs[0]) - .equip([ - neons[0], - childIndex, - neonResIds[0], - partIdForMask, - weaponResId, - ]) - ) - .to.emit(neon, "ChildAssetEquipped") - .withArgs( - neons[0], - neonResIds[0], - partIdForMask, - masks[0], - mask.address, - weaponResId - ); - - // All part slots are included on the response: - const expectedSlots = [bn(partIdForMask)]; - const expectedEquips = [ - [bn(neonResIds[0]), bn(weaponResId), bn(masks[0]), mask.address], - ]; - expect( - await view.getEquipped(neon.address, neons[0], neonResIds[0]) - ).to.eql([expectedSlots, expectedEquips]); - - // Child is marked as equipped: - expect( - await neon.isChildEquipped(neons[0], mask.address, masks[0]) - ).to.eql(true); - }); - - it("cannot equip non existing child in slot", async function () { - // Weapon is child on index 0 - const badChildIndex = 3; - const weaponResId = maskAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - neon - .connect(addrs[0]) - .equip([ - neons[0], - badChildIndex, - neonResIds[0], - partIdForMask, - weaponResId, - ]) - ).to.be.reverted; // Bad index - }); - }); - - describe("Compose", async function () { - it("can compose all parts for neon", async function () { - const childIndex = 0; - const weaponResId = maskAssetsEquip[0]; // This asset is assigned to weapon first weapon - await neon - .connect(addrs[0]) - .equip([ - neons[0], - childIndex, - neonResIds[0], - partIdForMask, - weaponResId, - ]); - - const expectedFixedParts = [ - [ - bn(partIdForHead1), // partId - 1, // z - "ipfs://head1.png", // metadataURI - ], - [ - bn(partIdForBody1), // partId - 1, // z - "ipfs://body1.png", // metadataURI - ], - [ - bn(partIdForHair1), // partId - 2, // z - "ipfs://hair1.png", // metadataURI - ], - ]; - const expectedSlotParts = [ - [ - bn(partIdForMask), // partId - bn(maskAssetsEquip[0]), // childAssetId - 2, // z - mask.address, // childAddress - bn(masks[0]), // childTokenId - "ipfs:weapon/equip/5", // childAssetMetadata - "", // partMetadata - ], - ]; - const allAssets = await view.composeEquippables( - neon.address, - neons[0], - neonResIds[0] - ); - expect(allAssets).to.eql([ - "ipfs:neonRes/1", // metadataURI - bn(0), // equippableGroupId - catalog.address, // baseAddress, - expectedFixedParts, - expectedSlotParts, - ]); - }); - - it("can compose all parts for mask", async function () { - const expectedFixedParts = [ - [ - bn(partIdForMaskCatalog1), // partId - 3, // z - "ipfs://maskCatalog1.png", // metadataURI - ], - [ - bn(partIdForHorns1), // partId - 5, // z - "ipfs://horn1.png", // metadataURI - ], - [ - bn(partIdForEars1), // partId - 4, // z - "ipfs://ears1.png", // metadataURI - ], - ]; - const allAssets = await view.composeEquippables( - mask.address, - masks[0], - maskAssetsEquip[0] - ); - expect(allAssets).to.eql([ - `ipfs:weapon/equip/${maskAssetsEquip[0]}`, // metadataURI - bn(maskpableGroupId), // equippableGroupId - catalog.address, // baseAddress - expectedFixedParts, - [], - ]); - }); - - it("cannot compose equippables for neon with not associated asset", async function () { - const wrongResId = maskAssetsEquip[1]; - await expect( - view.composeEquippables(mask.address, masks[0], wrongResId) - ).to.be.revertedWithCustomError(mask, "TokenDoesNotHaveAsset"); - }); - - it("cannot compose equippables for mask for asset with no catalog", async function () { - const noCatalogAssetId = 99; - await mask.addEquippableAssetEntry( - noCatalogAssetId, - 0, // Not meant to equip - ethers.constants.AddressZero, // Not meant to equip - `ipfs:weapon/full/customAsset.png`, - [] - ); - await mask.addAssetToToken(masks[0], noCatalogAssetId, 0); - await mask.connect(addrs[0]).acceptAsset(masks[0], 0, noCatalogAssetId); - await expect( - view.composeEquippables(mask.address, masks[0], noCatalogAssetId) - ).to.be.revertedWithCustomError(view, "NotComposableAsset"); - }); - }); -}); - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} diff --git a/assets/eip-6220/test/equippableSlotParts.ts b/assets/eip-6220/test/equippableSlotParts.ts deleted file mode 100644 index 8bb59ff..0000000 --- a/assets/eip-6220/test/equippableSlotParts.ts +++ /dev/null @@ -1,1130 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { - CatalogMock, - EquippableTokenMock, - EquipRenderUtils, -} from "../typechain-types"; - -const partIdForBody = 1; -const partIdForWeapon = 2; -const partIdForWeaponGem = 3; -const partIdForBackground = 4; - -const uniqueSnakeSoldiers = 10; -const uniqueWeapons = 4; -// const uniqueWeaponGems = 2; -// const uniqueBackgrounds = 3; - -const snakeSoldiersIds: number[] = []; -const weaponsIds: number[] = []; -const weaponGemsIds: number[] = []; -const backgroundsIds: number[] = []; - -const soldierResId = 100; -const weaponAssetsFull = [1, 2, 3, 4]; // Must match the total of uniqueAssets -const weaponAssetsEquip = [5, 6, 7, 8]; // Must match the total of uniqueAssets -const weaponGemAssetFull = 101; -const weaponGemAssetEquip = 102; -const backgroundAssetId = 200; - -enum ItemType { - None, - Slot, - Fixed, -} - -let addrs: SignerWithAddress[]; - -let nextTokenId = 1; -let nextChildTokenId = 100; - -async function mint(token: EquippableTokenMock, to: string): Promise { - const tokenId = nextTokenId; - nextTokenId++; - await token["mint(address,uint256)"](to, tokenId); - return tokenId; -} - -async function nestMint( - token: EquippableTokenMock, - to: string, - parentId: number -): Promise { - const childTokenId = nextChildTokenId; - nextChildTokenId++; - await token["nestMint(address,uint256,uint256)"](to, childTokenId, parentId); - return childTokenId; -} - -async function setupContextForSlots( - catalog: CatalogMock, - soldier: EquippableTokenMock, - weapon: EquippableTokenMock, - weaponGem: EquippableTokenMock, - background: EquippableTokenMock -) { - [, ...addrs] = await ethers.getSigners(); - - await setupCatalog(); - - await mintSnakeSoldiers(); - await mintWeapons(); - await mintWeaponGems(); - await mintBackgrounds(); - - await addAssetsToSoldier(); - await addAssetsToWeapon(); - await addAssetsToWeaponGem(); - await addAssetsToBackground(); - - return { - catalog, - soldier, - weapon, - background, - }; - - async function setupCatalog(): Promise { - const partForBody = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "genericBody.png", - }; - const partForWeapon = { - itemType: ItemType.Slot, - z: 2, - equippable: [weapon.address], - metadataURI: "", - }; - const partForWeaponGem = { - itemType: ItemType.Slot, - z: 3, - equippable: [weaponGem.address], - metadataURI: "noGem.png", - }; - const partForBackground = { - itemType: ItemType.Slot, - z: 0, - equippable: [background.address], - metadataURI: "noBackground.png", - }; - - await catalog.addPartList([ - { partId: partIdForBody, part: partForBody }, - { partId: partIdForWeapon, part: partForWeapon }, - { partId: partIdForWeaponGem, part: partForWeaponGem }, - { partId: partIdForBackground, part: partForBackground }, - ]); - } - - async function mintSnakeSoldiers(): Promise { - // This array is reused, so we "empty" it before - snakeSoldiersIds.length = 0; - // Using only first 3 addresses to mint - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await mint(soldier, addrs[i % 3].address); - snakeSoldiersIds.push(newId); - } - } - - async function mintWeapons(): Promise { - // This array is reused, so we "empty" it before - weaponsIds.length = 0; - // Mint one weapon to soldier - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await nestMint( - weapon, - soldier.address, - snakeSoldiersIds[i] - ); - weaponsIds.push(newId); - await soldier - .connect(addrs[i % 3]) - .acceptChild(snakeSoldiersIds[i], 0, weapon.address, newId); - } - } - - async function mintWeaponGems(): Promise { - // This array is reused, so we "empty" it before - weaponGemsIds.length = 0; - // Mint one weapon gem for each weapon on each soldier - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await nestMint(weaponGem, weapon.address, weaponsIds[i]); - weaponGemsIds.push(newId); - await weapon - .connect(addrs[i % 3]) - .acceptChild(weaponsIds[i], 0, weaponGem.address, newId); - } - } - - async function mintBackgrounds(): Promise { - // This array is reused, so we "empty" it before - backgroundsIds.length = 0; - // Mint one background to soldier - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await nestMint( - background, - soldier.address, - snakeSoldiersIds[i] - ); - backgroundsIds.push(newId); - await soldier - .connect(addrs[i % 3]) - .acceptChild(snakeSoldiersIds[i], 0, background.address, newId); - } - } - - async function addAssetsToSoldier(): Promise { - await soldier.addEquippableAssetEntry( - soldierResId, - 0, - catalog.address, - "ipfs:soldier/", - [partIdForBody, partIdForWeapon, partIdForBackground] - ); - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - await soldier.addAssetToToken(snakeSoldiersIds[i], soldierResId, 0); - await soldier - .connect(addrs[i % 3]) - .acceptAsset(snakeSoldiersIds[i], 0, soldierResId); - } - } - - async function addAssetsToWeapon(): Promise { - const equippableGroupId = 1; // Assets to equip will both use this - - for (let i = 0; i < weaponAssetsFull.length; i++) { - await weapon.addEquippableAssetEntry( - weaponAssetsFull[i], - 0, // Not meant to equip - ethers.constants.AddressZero, // Not meant to equip - `ipfs:weapon/full/${weaponAssetsFull[i]}`, - [] - ); - } - for (let i = 0; i < weaponAssetsEquip.length; i++) { - await weapon.addEquippableAssetEntry( - weaponAssetsEquip[i], - equippableGroupId, - catalog.address, - `ipfs:weapon/equip/${weaponAssetsEquip[i]}`, - [partIdForWeaponGem] - ); - } - - // Can be equipped into snakeSoldiers - await weapon.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partIdForWeapon - ); - - // Add 2 assets to each weapon, one full, one for equip - // There are 10 weapon tokens for 4 unique assets so we use % - for (let i = 0; i < weaponsIds.length; i++) { - await weapon.addAssetToToken( - weaponsIds[i], - weaponAssetsFull[i % uniqueWeapons], - 0 - ); - await weapon.addAssetToToken( - weaponsIds[i], - weaponAssetsEquip[i % uniqueWeapons], - 0 - ); - await weapon - .connect(addrs[i % 3]) - .acceptAsset(weaponsIds[i], 0, weaponAssetsFull[i % uniqueWeapons]); - await weapon - .connect(addrs[i % 3]) - .acceptAsset(weaponsIds[i], 0, weaponAssetsEquip[i % uniqueWeapons]); - } - } - - async function addAssetsToWeaponGem(): Promise { - const equippableGroupId = 1; // Assets to equip will use this - await weaponGem.addEquippableAssetEntry( - weaponGemAssetFull, - 0, // Not meant to equip - ethers.constants.AddressZero, // Not meant to equip - "ipfs:weagponGem/full/", - [] - ); - await weaponGem.addEquippableAssetEntry( - weaponGemAssetEquip, - equippableGroupId, - catalog.address, - "ipfs:weagponGem/equip/", - [] - ); - await weaponGem.setValidParentForEquippableGroup( - // Can be equipped into weapons - equippableGroupId, - weapon.address, - partIdForWeaponGem - ); - - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - await weaponGem.addAssetToToken(weaponGemsIds[i], weaponGemAssetFull, 0); - await weaponGem.addAssetToToken(weaponGemsIds[i], weaponGemAssetEquip, 0); - await weaponGem - .connect(addrs[i % 3]) - .acceptAsset(weaponGemsIds[i], 0, weaponGemAssetFull); - await weaponGem - .connect(addrs[i % 3]) - .acceptAsset(weaponGemsIds[i], 0, weaponGemAssetEquip); - } - } - - async function addAssetsToBackground(): Promise { - const equippableGroupId = 1; // Assets to equip will use this - await background.addEquippableAssetEntry( - backgroundAssetId, - equippableGroupId, - catalog.address, - "ipfs:background/", - [] - ); - // Can be equipped into snakeSoldiers - await background.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partIdForBackground - ); - - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - await background.addAssetToToken(backgroundsIds[i], backgroundAssetId, 0); - await background - .connect(addrs[i % 3]) - .acceptAsset(backgroundsIds[i], 0, backgroundAssetId); - } - } -} - -async function slotsFixture() { - const catalogSymbol = "SSB"; - const catalogType = "mixed"; - - const catalogFactory = await ethers.getContractFactory("CatalogMock"); - const equipFactory = await ethers.getContractFactory("EquippableTokenMock"); - const viewFactory = await ethers.getContractFactory("EquipRenderUtils"); - - // View - const view = await viewFactory.deploy(); - await view.deployed(); - - // Catalog - const catalog = ( - await catalogFactory.deploy(catalogSymbol, catalogType) - ); - await catalog.deployed(); - - // Soldier token - const soldier = await equipFactory.deploy(); - await soldier.deployed(); - - // Weapon - const weapon = await equipFactory.deploy(); - await weapon.deployed(); - - // Weapon Gem - const weaponGem = await equipFactory.deploy(); - await weaponGem.deployed(); - - // Background - const background = await equipFactory.deploy(); - await background.deployed(); - - await setupContextForSlots(catalog, soldier, weapon, weaponGem, background); - - return { catalog, soldier, weapon, weaponGem, background, view }; -} - -// The general idea is having these tokens: Soldier, Weapon, WeaponGem and Background. -// Weapon and Background can be equipped into Soldier. WeaponGem can be equipped into Weapon -// All use a single catalog. -// Soldier will use a single enumerated fixed asset for simplicity -// Weapon will have 2 assets per weapon, one for full view, one for equipping -// Background will have a single asset for each, it can be used as full view and to equip -// Weapon Gems will have 2 enumerated assets, one for full view, one for equipping. -describe("EquippableTokenMock with Slots", async () => { - let catalog: CatalogMock; - let soldier: EquippableTokenMock; - let weapon: EquippableTokenMock; - let weaponGem: EquippableTokenMock; - let background: EquippableTokenMock; - let view: EquipRenderUtils; - - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [, ...addrs] = await ethers.getSigners(); - ({ catalog, soldier, weapon, weaponGem, background, view } = - await loadFixture(slotsFixture)); - }); - - it("can support IERC6220", async function () { - expect(await soldier.supportsInterface("0x28bc9ae4")).to.equal(true); - }); - - describe("Validations", async function () { - it("can validate equips of weapons into snakeSoldiers", async function () { - // This asset is not equippable - expect( - await weapon.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - weaponsIds[0], - weaponAssetsFull[0], - partIdForWeapon - ) - ).to.eql(false); - - // This asset is equippable into weapon part - expect( - await weapon.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - weaponsIds[0], - weaponAssetsEquip[0], - partIdForWeapon - ) - ).to.eql(true); - - // This asset is NOT equippable into weapon gem part - expect( - await weapon.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - weaponsIds[0], - weaponAssetsEquip[0], - partIdForWeaponGem - ) - ).to.eql(false); - }); - - it("can validate equips of weapon gems into weapons", async function () { - // This asset is not equippable - expect( - await weaponGem.canTokenBeEquippedWithAssetIntoSlot( - weapon.address, - weaponGemsIds[0], - weaponGemAssetFull, - partIdForWeaponGem - ) - ).to.eql(false); - - // This asset is equippable into weapon gem slot - expect( - await weaponGem.canTokenBeEquippedWithAssetIntoSlot( - weapon.address, - weaponGemsIds[0], - weaponGemAssetEquip, - partIdForWeaponGem - ) - ).to.eql(true); - - // This asset is NOT equippable into background slot - expect( - await weaponGem.canTokenBeEquippedWithAssetIntoSlot( - weapon.address, - weaponGemsIds[0], - weaponGemAssetEquip, - partIdForBackground - ) - ).to.eql(false); - }); - - it("can validate equips of backgrounds into snakeSoldiers", async function () { - // This asset is equippable into background slot - expect( - await background.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - backgroundsIds[0], - backgroundAssetId, - partIdForBackground - ) - ).to.eql(true); - - // This asset is NOT equippable into weapon slot - expect( - await background.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - backgroundsIds[0], - backgroundAssetId, - partIdForWeapon - ) - ).to.eql(false); - }); - }); - - describe("Equip", async function () { - it("can equip weapon", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await equipWeaponAndCheckFromAddress( - soldierOwner, - childIndex, - weaponResId - ); - }); - - it("can equip weapon if approved", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const approved = addrs[1]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(soldierOwner) - .approve(approved.address, snakeSoldiersIds[0]); - await equipWeaponAndCheckFromAddress(approved, childIndex, weaponResId); - }); - - it("can equip weapon if approved for all", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const approved = addrs[1]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(soldierOwner) - .setApprovalForAll(approved.address, true); - await equipWeaponAndCheckFromAddress(approved, childIndex, weaponResId); - }); - - it("can equip weapon and background", async function () { - // Weapon is child on index 0, background on index 1 - const weaponChildIndex = 0; - const backgroundChildIndex = 1; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - weaponChildIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - backgroundChildIndex, - soldierResId, - partIdForBackground, - backgroundAssetId, - ]); - - const expectedSlots = [bn(partIdForWeapon), bn(partIdForBackground)]; - const expectedEquips = [ - [bn(soldierResId), bn(weaponResId), bn(weaponsIds[0]), weapon.address], - [ - bn(soldierResId), - bn(backgroundAssetId), - bn(backgroundsIds[0]), - background.address, - ], - ]; - expect( - await view.getEquipped( - soldier.address, - snakeSoldiersIds[0], - soldierResId - ) - ).to.eql([expectedSlots, expectedEquips]); - - // Children are marked as equipped: - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - weapon.address, - weaponsIds[0] - ) - ).to.eql(true); - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - background.address, - backgroundsIds[0] - ) - ).to.eql(true); - }); - - it("cannot equip non existing child in slot (weapon in background)", async function () { - // Weapon is child on index 0, background on index 1 - const badChildIndex = 3; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - badChildIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.reverted; // Bad index - }); - - it("cannot set a valid equippable group with id 0", async function () { - const equippableGroupId = 0; - // The malicious child indicates it can be equipped into soldier: - await expect( - weaponGem.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partIdForWeaponGem - ) - ).to.be.revertedWithCustomError(weaponGem, "IdZeroForbidden"); - }); - - it("cannot set a valid equippable group with part id 0", async function () { - const equippableGroupId = 1; - const partId = 0; - // The malicious child indicates it can be equipped into soldier: - await expect( - weaponGem.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partId - ) - ).to.be.revertedWithCustomError(weaponGem, "IdZeroForbidden"); - }); - - it("cannot equip into a slot not set on the parent asset (gem into soldier)", async function () { - const soldierOwner = addrs[0]; - const soldierId = snakeSoldiersIds[0]; - const childIndex = 2; - - const newWeaponGemId = await nestMint( - weaponGem, - soldier.address, - soldierId - ); - await soldier - .connect(soldierOwner) - .acceptChild(soldierId, 0, weaponGem.address, newWeaponGemId); - - // Add assets to weapon - await weaponGem.addAssetToToken(newWeaponGemId, weaponGemAssetFull, 0); - await weaponGem.addAssetToToken(newWeaponGemId, weaponGemAssetEquip, 0); - await weaponGem - .connect(soldierOwner) - .acceptAsset(newWeaponGemId, 0, weaponGemAssetFull); - await weaponGem - .connect(soldierOwner) - .acceptAsset(newWeaponGemId, 0, weaponGemAssetEquip); - - // The malicious child indicates it can be equipped into soldier: - await weaponGem.setValidParentForEquippableGroup( - 1, // equippableGroupId for gems - soldier.address, - partIdForWeaponGem - ); - - // Weapon is child on index 0, background on index 1 - await expect( - soldier - .connect(addrs[0]) - .equip([ - soldierId, - childIndex, - soldierResId, - partIdForWeaponGem, - weaponGemAssetEquip, - ]) - ).to.be.revertedWithCustomError(soldier, "TargetAssetCannotReceiveSlot"); - }); - - it("cannot equip wrong child in slot (weapon in background)", async function () { - // Weapon is child on index 0, background on index 1 - const backgroundChildIndex = 1; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - backgroundChildIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "TokenCannotBeEquippedWithAssetIntoSlot" - ); - }); - - it("cannot equip child in wrong slot (weapon in background)", async function () { - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForBackground, - weaponResId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "TokenCannotBeEquippedWithAssetIntoSlot" - ); - }); - - it("cannot equip child with wrong asset (weapon in background)", async function () { - const childIndex = 0; - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - backgroundAssetId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "TokenCannotBeEquippedWithAssetIntoSlot" - ); - }); - - it("cannot equip if not owner", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[1]) // Owner is addrs[0] - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.revertedWithCustomError(soldier, "ERC721NotApprovedOrOwner"); - }); - - it("cannot equip 2 children into the same slot", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - const weaponAssetIndex = 3; - await mintWeaponToSoldier( - addrs[0], - snakeSoldiersIds[0], - weaponAssetIndex - ); - - const newWeaponChildIndex = 2; - const newWeaponResId = weaponAssetsEquip[weaponAssetIndex]; - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - newWeaponChildIndex, - soldierResId, - partIdForWeapon, - newWeaponResId, - ]) - ).to.be.revertedWithCustomError(soldier, "SlotAlreadyUsed"); - }); - - it("cannot equip if not intented on catalog", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - - // Remove equippable addresses for part. - await catalog.resetEquippableAddresses(partIdForWeapon); - await expect( - soldier - .connect(addrs[0]) // Owner is addrs[0] - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "EquippableEquipNotAllowedByCatalog" - ); - }); - }); - - describe("Unequip", async function () { - it("can unequip", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await unequipWeaponAndCheckFromAddress(soldierOwner); - }); - - it("can unequip if approved", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - const approved = addrs[1]; - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await soldier - .connect(soldierOwner) - .approve(approved.address, snakeSoldiersIds[0]); - await unequipWeaponAndCheckFromAddress(approved); - }); - - it("can unequip if approved for all", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - const approved = addrs[1]; - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await soldier - .connect(soldierOwner) - .setApprovalForAll(approved.address, true); - await unequipWeaponAndCheckFromAddress(approved); - }); - - it("cannot unequip if not equipped", async function () { - await expect( - soldier - .connect(addrs[0]) - .unequip(snakeSoldiersIds[0], soldierResId, partIdForWeapon) - ).to.be.revertedWithCustomError(soldier, "NotEquipped"); - }); - - it("cannot unequip if not owner", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await expect( - soldier - .connect(addrs[1]) - .unequip(snakeSoldiersIds[0], soldierResId, partIdForWeapon) - ).to.be.revertedWithCustomError(soldier, "ERC721NotApprovedOrOwner"); - }); - }); - - describe("Transfer equipped", async function () { - it("can unequip and transfer child", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await unequipWeaponAndCheckFromAddress(soldierOwner); - await soldier - .connect(soldierOwner) - .transferChild( - snakeSoldiersIds[0], - soldierOwner.address, - 0, - childIndex, - weapon.address, - weaponsIds[0], - false, - "0x" - ); - }); - - it("child transfer fails if child is equipped", async function () { - const soldierOwner = addrs[0]; - // Weapon is child on index 0 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await expect( - soldier - .connect(soldierOwner) - .transferChild( - snakeSoldiersIds[0], - soldierOwner.address, - 0, - childIndex, - weapon.address, - weaponsIds[0], - false, - "0x" - ) - ).to.be.revertedWithCustomError(weapon, "MustUnequipFirst"); - }); - }); - - describe("Compose", async function () { - it("can compose equippables for soldier", async function () { - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - const expectedFixedParts = [ - [ - bn(partIdForBody), // partId - 1, // z - "genericBody.png", // metadataURI - ], - ]; - const expectedSlotParts = [ - [ - bn(partIdForWeapon), // partId - bn(weaponAssetsEquip[0]), // childAssetId - 2, // z - weapon.address, // childAddress - bn(weaponsIds[0]), // childTokenId - "ipfs:weapon/equip/5", // childAssetMetadata - "", // partMetadata - ], - [ - // Nothing on equipped on background slot: - bn(partIdForBackground), // partId - bn(0), // childAssetId - 0, // z - ethers.constants.AddressZero, // childAddress - bn(0), // childTokenId - "", // childAssetMetadata - "noBackground.png", // partMetadata - ], - ]; - const allAssets = await view.composeEquippables( - soldier.address, - snakeSoldiersIds[0], - soldierResId - ); - expect(allAssets).to.eql([ - "ipfs:soldier/", // metadataURI - bn(0), // equippableGroupId - catalog.address, // catalogAddress - expectedFixedParts, - expectedSlotParts, - ]); - }); - - it("can compose equippables for simple asset", async function () { - const allAssets = await view.composeEquippables( - background.address, - backgroundsIds[0], - backgroundAssetId - ); - expect(allAssets).to.eql([ - "ipfs:background/", // metadataURI - bn(1), // equippableGroupId - catalog.address, // catalogAddress, - [], - [], - ]); - }); - - it("cannot compose equippables for soldier with not associated asset", async function () { - const wrongResId = weaponAssetsEquip[1]; - await expect( - view.composeEquippables(weapon.address, weaponsIds[0], wrongResId) - ).to.be.revertedWithCustomError(weapon, "TokenDoesNotHaveAsset"); - }); - }); - - async function equipWeaponAndCheckFromAddress( - from: SignerWithAddress, - childIndex: number, - weaponResId: number - ): Promise { - await expect( - soldier - .connect(from) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ) - .to.emit(soldier, "ChildAssetEquipped") - .withArgs( - snakeSoldiersIds[0], - soldierResId, - partIdForWeapon, - weaponsIds[0], - weapon.address, - weaponAssetsEquip[0] - ); - // All part slots are included on the response: - const expectedSlots = [bn(partIdForWeapon), bn(partIdForBackground)]; - // If a slot has nothing equipped, it returns an empty equip: - const expectedEquips = [ - [bn(soldierResId), bn(weaponResId), bn(weaponsIds[0]), weapon.address], - [bn(0), bn(0), bn(0), ethers.constants.AddressZero], - ]; - expect( - await view.getEquipped(soldier.address, snakeSoldiersIds[0], soldierResId) - ).to.eql([expectedSlots, expectedEquips]); - - // Child is marked as equipped: - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - weapon.address, - weaponsIds[0] - ) - ).to.eql(true); - } - - async function unequipWeaponAndCheckFromAddress( - from: SignerWithAddress - ): Promise { - await expect( - soldier - .connect(from) - .unequip(snakeSoldiersIds[0], soldierResId, partIdForWeapon) - ) - .to.emit(soldier, "ChildAssetUnequipped") - .withArgs( - snakeSoldiersIds[0], - soldierResId, - partIdForWeapon, - weaponsIds[0], - weapon.address, - weaponAssetsEquip[0] - ); - - const expectedSlots = [bn(partIdForWeapon), bn(partIdForBackground)]; - // If a slot has nothing equipped, it returns an empty equip: - const expectedEquips = [ - [bn(0), bn(0), bn(0), ethers.constants.AddressZero], - [bn(0), bn(0), bn(0), ethers.constants.AddressZero], - ]; - expect( - await view.getEquipped(soldier.address, snakeSoldiersIds[0], soldierResId) - ).to.eql([expectedSlots, expectedEquips]); - - // Child is marked as not equipped: - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - weapon.address, - weaponsIds[0] - ) - ).to.eql(false); - } - - async function mintWeaponToSoldier( - soldierOwner: SignerWithAddress, - soldierId: number, - assetIndex: number - ): Promise { - // Mint another weapon to the soldier and accept it - const newWeaponId = await nestMint(weapon, soldier.address, soldierId); - await soldier - .connect(soldierOwner) - .acceptChild(soldierId, 0, weapon.address, newWeaponId); - - // Add assets to weapon - await weapon.addAssetToToken(newWeaponId, weaponAssetsFull[assetIndex], 0); - await weapon.addAssetToToken(newWeaponId, weaponAssetsEquip[assetIndex], 0); - await weapon - .connect(soldierOwner) - .acceptAsset(newWeaponId, 0, weaponAssetsFull[assetIndex]); - await weapon - .connect(soldierOwner) - .acceptAsset(newWeaponId, 0, weaponAssetsEquip[assetIndex]); - - return newWeaponId; - } -}); - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} diff --git a/assets/eip-6220/test/multiasset.ts b/assets/eip-6220/test/multiasset.ts deleted file mode 100644 index 2074298..0000000 --- a/assets/eip-6220/test/multiasset.ts +++ /dev/null @@ -1,711 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { - EquippableTokenMock, - ERC721ReceiverMock, - MultiAssetRenderUtils, - NonReceiverMock, -} from "../typechain-types"; - -describe("MultiAsset", async () => { - let token: EquippableTokenMock; - let renderUtils: MultiAssetRenderUtils; - let nonReceiver: NonReceiverMock; - let receiver721: ERC721ReceiverMock; - - let owner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - const metaURIDefault = "metaURI"; - - beforeEach(async () => { - [owner, ...addrs] = await ethers.getSigners(); - - const equppableFactory = await ethers.getContractFactory( - "EquippableTokenMock" - ); - token = await equppableFactory.deploy(); - await token.deployed(); - - const renderFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - renderUtils = await renderFactory.deploy(); - await renderUtils.deployed(); - }); - - describe("ERC165 check", async function () { - it("can support IERC165", async function () { - expect(await token.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("can support IERC721", async function () { - expect(await token.supportsInterface("0x80ac58cd")).to.equal(true); - }); - - it("can support IMultiAsset", async function () { - expect(await token.supportsInterface("0xd1526708")).to.equal(true); - }); - - it("cannot support other interfaceId", async function () { - expect(await token.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("Check OnReceived ERC721 and Multiasset", async function () { - it("Revert on transfer to non onERC721/onMultiasset implementer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const NonReceiver = await ethers.getContractFactory("NonReceiverMock"); - nonReceiver = await NonReceiver.deploy(); - await nonReceiver.deployed(); - - await expect( - token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - nonReceiver.address, - 1 - ) - ).to.be.revertedWithCustomError( - token, - "ERC721TransferToNonReceiverImplementer" - ); - }); - - it("onERC721Received callback on transfer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const ERC721Receiver = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - receiver721 = await ERC721Receiver.deploy(); - await receiver721.deployed(); - - await token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - receiver721.address, - 1 - ); - expect(await token.ownerOf(1)).to.equal(receiver721.address); - }); - }); - - describe("Asset storage", async function () { - it("can add asset", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - }); - - it("cannot get non existing asset", async function () { - const tokenId = 1; - const resId = 10; - await token.mint(owner.address, tokenId); - await expect( - token.getAssetMetadata(tokenId, resId) - ).to.be.revertedWithCustomError(token, "TokenDoesNotHaveAsset"); - }); - - it("cannot add asset entry if not issuer", async function () { - const id = 10; - await expect( - token.connect(addrs[1]).addAssetEntry(id, metaURIDefault) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("can set and get issuer", async function () { - const newIssuerAddr = addrs[1].address; - expect(await token.getIssuer()).to.equal(owner.address); - - await token.setIssuer(newIssuerAddr); - expect(await token.getIssuer()).to.equal(newIssuerAddr); - }); - - it("cannot set issuer if not issuer", async function () { - const newIssuer = addrs[1]; - await expect( - token.connect(newIssuer).setIssuer(newIssuer.address) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("cannot overwrite asset", async function () { - const id = 10; - - await token.addAssetEntry(id, metaURIDefault); - await expect( - token.addAssetEntry(id, metaURIDefault) - ).to.be.revertedWithCustomError(token, "AssetAlreadyExists"); - }); - - it("cannot add asset with id 0", async function () { - const id = ethers.utils.hexZeroPad("0x0", 8); - - await expect( - token.addAssetEntry(id, metaURIDefault) - ).to.be.revertedWithCustomError(token, "IdZeroForbidden"); - }); - - it("cannot add same asset twice", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - - await expect( - token.addAssetEntry(id, metaURIDefault) - ).to.be.revertedWithCustomError(token, "AssetAlreadyExists"); - }); - }); - - describe("Adding assets", async function () { - it("can add asset to token", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId2, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - - const pendingIds = await token.getPendingAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, pendingIds) - ).to.be.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot add non existing asset to token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect( - token.addAssetToToken(tokenId, resId, 0) - ).to.be.revertedWithCustomError(token, "NoAssetMatchingId"); - }); - - it("can add asset to non existing token and it is pending when minted", async function () { - const resId = 1; - const tokenId = 1; - await addAssets([resId]); - - await token.addAssetToToken(tokenId, resId, 0); - await token.mint(owner.address, tokenId); - expect(await token.getPendingAssets(tokenId)).to.eql([ - ethers.BigNumber.from(resId), - ]); - }); - - it("cannot add asset twice to the same token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.addAssetToToken(tokenId, ethers.BigNumber.from(resId), 0) - ).to.be.revertedWithCustomError(token, "AssetAlreadyExists"); - }); - - it("cannot add too many assets to the same token", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - for (let i = 1; i <= 128; i++) { - await addAssets([i]); - await token.addAssetToToken(tokenId, i, 0); - } - - // Now it's full, next should fail - const resId = 129; - await addAssets([resId]); - await expect( - token.addAssetToToken(tokenId, resId, 0) - ).to.be.revertedWithCustomError(token, "MaxPendingAssetsReached"); - }); - - it("can add same asset to 2 different tokens", async function () { - const resId = 1; - const tokenId1 = 1; - const tokenId2 = 2; - - await token.mint(owner.address, tokenId1); - await token.mint(owner.address, tokenId2); - await addAssets([resId]); - await token.addAssetToToken(tokenId1, resId, 0); - await token.addAssetToToken(tokenId2, resId, 0); - }); - }); - - describe("Accepting assets", async function () { - it("can accept asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept multiple assets", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 1, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot accept asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - }); - - it("cannot accept asset if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.connect(addrs[1]).acceptAsset(tokenId, 0, resId) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - }); - - it("cannot accept non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect( - token.acceptAsset(tokenId, 0, 1) - ).to.be.revertedWithCustomError(token, "IndexOutOfRange"); - }); - }); - - describe("Overwriting assets", async function () { - it("can add asset to token overwritting an existing one", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Add new asset to overwrite the first, and accept - const activeAssets = await token.getActiveAssets(tokenId); - await expect(token.addAssetToToken(tokenId, resId2, activeAssets[0])) - .to.emit(token, "AssetAddedToTokens") - .withArgs([tokenId], resId2, resId); - const pendingAssets = await token.getPendingAssets(tokenId); - - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(activeAssets[0]); - await expect(token.acceptAsset(tokenId, 0, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - // Overwrite should be gone - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(ethers.BigNumber.from(0)); - }); - - it("can overwrite non existing asset to token, it could have been deleted", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken( - tokenId, - resId, - ethers.utils.hexZeroPad("0x1", 8) - ); - await token.acceptAsset(tokenId, 0, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - }); - }); - - describe("Rejecting assets", async function () { - it("can reject asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject all assets if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject asset and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject it - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAsset(tokenId, 0, resId2); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all assets and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject all - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAllAssets(tokenId, 1); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all pending assets at max capacity", async function () { - const tokenId = 1; - const resArr = []; - - for (let i = 1; i < 128; i++) { - resArr.push(i); - } - - await token.mint(owner.address, tokenId); - await addAssets(resArr); - - for (let i = 1; i < 128; i++) { - await token.addAssetToToken(tokenId, i, 1); - } - await token.rejectAllAssets(tokenId, 128); - - expect(await token.getAssetReplacements(1, 2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("cannot reject asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.rejectAsset(tokenId, 0, resId); - }); - - it("cannot reject asset nor reject all if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(addrs[1]).rejectAsset(tokenId, 0, resId) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - await expect( - token.connect(addrs[1]).rejectAllAssets(tokenId, 1) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - }); - - it("cannot reject non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect( - token.rejectAsset(tokenId, 0, 1) - ).to.be.revertedWithCustomError(token, "IndexOutOfRange"); - }); - }); - - describe("Priorities", async function () { - it("can set and get priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([0, 1]); - await expect(token.setPriority(tokenId, [2, 1])) - .to.emit(token, "AssetPrioritySet") - .withArgs(tokenId); - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([2, 1]); - }); - - it("cannot set priorities for non owned token", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [2, 1]) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - }); - - it("cannot set different number of priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.setPriority(tokenId, [1]) - ).to.be.revertedWithCustomError(token, "BadPriorityListLength"); - await expect( - token.setPriority(tokenId, [2, 1, 3]) - ).to.be.revertedWithCustomError(token, "BadPriorityListLength"); - }); - - it("cannot set priorities for non existing token", async function () { - const tokenId = 1; - await expect( - token.connect(addrs[1]).setPriority(tokenId, []) - ).to.be.revertedWithCustomError(token, "ERC721InvalidTokenId"); - }); - }); - - describe("Approval Cleaning", async function () { - it("cleans token and assets approvals on transfer", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const newOwner = addrs[2]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner).transfer(newOwner.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql( - ethers.constants.AddressZero - ); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - ethers.constants.AddressZero - ); - }); - - it("cleans token and assets approvals on burn", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner)["burn(uint256)"](tokenId); - - await expect(token.getApproved(tokenId)).to.be.revertedWithCustomError( - token, - "ERC721InvalidTokenId" - ); - await expect( - token.getApprovedForAssets(tokenId) - ).to.be.revertedWithCustomError(token, "ERC721InvalidTokenId"); - }); - }); - - async function mintSampleToken(): Promise<{ - tokenOwner: SignerWithAddress; - tokenId: number; - }> { - const tokenOwner = owner; - const tokenId = 1; - await token.mint(tokenOwner.address, tokenId); - - return { tokenOwner, tokenId }; - } - - async function addAssets(ids: number[]): Promise { - ids.forEach(async (resId) => { - await token.addAssetEntry(resId, metaURIDefault); - }); - } - - async function addAssetsToToken(tokenId: number): Promise { - const resId = 1; - const resId2 = 2; - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await token.acceptAsset(tokenId, 0, resId); - await token.acceptAsset(tokenId, 0, resId2); - } - - async function checkAcceptFromAddress( - accepter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect(token.connect(accepter).acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - } - - async function checkRejectFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(rejecter).rejectAsset(tokenId, 0, resId) - ).to.emit(token, "AssetRejected"); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } - - async function checkRejectAllFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - const resId2 = 2; - - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - - await expect(token.connect(rejecter).rejectAllAssets(tokenId, 2)).to.emit( - token, - "AssetRejected" - ); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } -}); diff --git a/assets/eip-6220/test/nestable.ts b/assets/eip-6220/test/nestable.ts deleted file mode 100644 index 8c4fbc6..0000000 --- a/assets/eip-6220/test/nestable.ts +++ /dev/null @@ -1,1493 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber, constants } from "ethers"; -import { ethers } from "hardhat"; -import { EquippableTokenMock } from "../typechain-types"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -const ADDRESS_ZERO = constants.AddressZero; - -async function parentChildFixture(): Promise<{ - parent: EquippableTokenMock; - child: EquippableTokenMock; -}> { - const factory = await ethers.getContractFactory("EquippableTokenMock"); - - const parent = await factory.deploy(); - await parent.deployed(); - const child = await factory.deploy(); - await child.deployed(); - return { parent, child }; -} - -describe("NestableToken", function () { - let parent: EquippableTokenMock; - let child: EquippableTokenMock; - let owner: SignerWithAddress; - let tokenOwner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [owner, tokenOwner, ...addrs] = await ethers.getSigners(); - ({ parent, child } = await loadFixture(parentChildFixture)); - }); - - describe("Minting", async function () { - it("cannot mint id 0", async function () { - const tokenId1 = 0; - await expect( - child.mint(owner.address, tokenId1) - ).to.be.revertedWithCustomError(child, "IdZeroForbidden"); - }); - - it("cannot nest mint id 0", async function () { - const parentId = 1; - await child.mint(owner.address, parentId); - const childId1 = 0; - await expect( - child.nestMint(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "IdZeroForbidden"); - }); - - it("cannot mint already minted token", async function () { - const tokenId1 = 1; - await child.mint(owner.address, tokenId1); - await expect( - child.mint(owner.address, tokenId1) - ).to.be.revertedWithCustomError(child, "ERC721TokenAlreadyMinted"); - }); - - it("cannot nest mint already minted token", async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "ERC721TokenAlreadyMinted"); - }); - - it("cannot nest mint already minted token", async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "ERC721TokenAlreadyMinted"); - }); - - it("can mint with no destination", async function () { - const tokenId1 = 1; - await child.mint(tokenOwner.address, tokenId1); - expect(await child.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await child.directOwnerOf(tokenId1)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - }); - - it("has right owners", async function () { - const otherOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(tokenOwner.address, tokenId1); - const tokenId2 = 2; - await parent.mint(otherOwner.address, tokenId2); - const tokenId3 = 3; - await parent.mint(otherOwner.address, tokenId3); - - expect(await parent.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await parent.ownerOf(tokenId2)).to.equal(otherOwner.address); - expect(await parent.ownerOf(tokenId3)).to.equal(otherOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(2); - - await expect(parent.ownerOf(9999)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - }); - - it("cannot mint to zero address", async function () { - await expect(child.mint(ADDRESS_ZERO, 1)).to.be.revertedWithCustomError( - child, - "ERC721MintToTheZeroAddress" - ); - }); - - it("cannot nest mint to a non-contract destination", async function () { - await expect( - child.nestMint(tokenOwner.address, 1, 1) - ).to.be.revertedWithCustomError(child, "IsNotContract"); - }); - - it("cannot nest mint to non nestable receiver", async function () { - const ERC721 = await ethers.getContractFactory("ERC721Mock"); - const nonReceiver = await ERC721.deploy("Non receiver", "NR"); - await nonReceiver.deployed(); - - await expect( - child.nestMint(nonReceiver.address, 1, 1) - ).to.be.revertedWithCustomError(child, "MintToNonNestableImplementer"); - }); - - it("cannot nest mint to a non-existent token", async function () { - await expect( - child.nestMint(parent.address, 1, 1) - ).to.be.revertedWithCustomError(child, "ERC721InvalidTokenId"); - }); - - it("cannot nest mint to zero address", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect( - child.nestMint(ADDRESS_ZERO, parentId, 1) - ).to.be.revertedWithCustomError(child, "IsNotContract"); - }); - - it("can mint to contract and owners are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // owner is the same adress - expect(await parent.ownerOf(parentId)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - }); - - it("can mint to contract and direct owners are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Direct owner is an address for the parent - expect(await parent.directOwnerOf(parentId)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - // Direct owner is a contract for the child - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(parentId), - true, - ]); - }); - - it("can mint to contract and parent's children are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - const children = await parent.childrenOf(parentId); - expect(children).to.eql([]); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([[bn(childId1), child.address]]); - expect(await parent.pendingChildOf(parentId, 0)).to.eql([ - bn(childId1), - child.address, - ]); - }); - - it("cannot get child out of index", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.childOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - "ChildIndexOutOfRange" - ); - }); - - it("cannot get pending child out of index", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect( - parent.pendingChildOf(parentId, 0) - ).to.be.revertedWithCustomError(parent, "PendingChildIndexOutOfRange"); - }); - - it("can mint multiple children", async function () { - const parentId = 1; - const childId1 = 99; - const childId2 = 100; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId2)).to.equal(tokenOwner.address); - - expect(await child.balanceOf(parent.address)).to.equal(2); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([ - [bn(childId1), child.address], - [bn(childId2), child.address], - ]); - }); - - it("can mint child into child", async function () { - const parentId = 1; - const childId1 = 99; - const granchildId = 999; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - - // Check balances -- yes, technically the counted balance indicates `child` owns an instance of itself - // and this is a little counterintuitive, but the root owner is the EOA. - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - const pendingChildrenOfChunky10 = await parent.pendingChildrenOf( - parentId - ); - const pendingChildrenOfMonkey1 = await child.pendingChildrenOf(childId1); - - expect(pendingChildrenOfChunky10).to.eql([[bn(childId1), child.address]]); - expect(pendingChildrenOfMonkey1).to.eql([ - [bn(granchildId), child.address], - ]); - - expect(await child.directOwnerOf(granchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - - expect(await child.ownerOf(granchildId)).to.eql(tokenOwner.address); - }); - - it("cannot have too many pending children", async () => { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - - // First 128 should be fine. - for (let i = 1; i <= 128; i++) { - await child.nestMint(parent.address, i, parentId); - } - - await expect( - child.nestMint(parent.address, 129, parentId) - ).to.be.revertedWithCustomError(child, "MaxPendingChildrenReached"); - }); - }); - - describe("Interface support", async function () { - it("can support IERC165", async function () { - expect(await parent.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("can support IERC721", async function () { - expect(await parent.supportsInterface("0x80ac58cd")).to.equal(true); - }); - - it("can support INestable", async function () { - expect(await parent.supportsInterface("0x42b0e56f")).to.equal(true); - }); - - it("cannot support other interfaceId", async function () { - expect(await parent.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("Adding child", async function () { - it("cannot add child from user address", async function () { - const tokenOwner1 = addrs[0]; - const tokenOwner2 = addrs[1]; - const parentId = 1; - await parent.mint(tokenOwner1.address, parentId); - const childId1 = 99; - await child.mint(tokenOwner2.address, childId1); - await expect( - parent.addChild(parentId, childId1, "0x") - ).to.be.revertedWithCustomError(parent, "IsNotContract"); - }); - }); - - describe("Accept child", async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it("can accept child", async function () { - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1) - ) - .to.emit(parent, "ChildAccepted") - .withArgs(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it("can accept child if approved", async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent - .connect(approved) - .acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it("can accept child if approved for all", async function () { - const operator = addrs[2]; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - await parent - .connect(operator) - .acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it("cannot accept not owned child", async function () { - const notOwner = addrs[3]; - await expect( - parent - .connect(notOwner) - .acceptChild(parentId, 0, child.address, childId1) - ).to.be.revertedWithCustomError(parent, "ERC721NotApprovedOrOwner"); - }); - - it("cannot accept child if address or id do not match", async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, otherChildId) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 0, otherAddress, childId1) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - }); - - it("cannot accept children for non existing index", async () => { - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 1, child.address, childId1) - ).to.be.revertedWithCustomError(parent, "PendingChildIndexOutOfRange"); - }); - - async function checkChildWasAccepted() { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([ - [bn(childId1), child.address], - ]); - } - }); - - describe("Rejecting children", async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, 99, parentId); - }); - - it("can reject all pending children", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect(parent.connect(tokenOwner).rejectAllChildren(parentId, 3)) - .to.emit(parent, "AllChildrenRejected") - .withArgs(parentId); - await checkNoChildrenNorPending(parentId); - - // They are still on the child - expect(await child.balanceOf(parent.address)).to.equal(3); - }); - - it("cannot reject all pending children if there are more than expected", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect( - parent.connect(tokenOwner).rejectAllChildren(parentId, 1) - ).to.be.revertedWithCustomError(parent, "UnexpectedNumberOfChildren"); - }); - - it("can reject all pending children if approved", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const rejecter = addrs[1]; - await parent.connect(tokenOwner).approve(rejecter.address, parentId); - await parent.connect(rejecter).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it("can reject all pending children if approved for all", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const operator = addrs[2]; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - await parent.connect(operator).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it("cannot reject all pending children for not owned pending child", async function () { - const notOwner = addrs[3]; - - await expect( - parent.connect(notOwner).rejectAllChildren(parentId, 2) - ).to.be.revertedWithCustomError(parent, "ERC721NotApprovedOrOwner"); - }); - }); - - describe("Burning", async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - }); - - it("can burn token", async function () { - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - await parent.connect(tokenOwner)["burn(uint256)"](parentId); - await checkBurntParent(); - }); - - it("can burn token if approved", async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved)["burn(uint256)"](parentId); - await checkBurntParent(); - }); - - it("can burn token if approved for all", async function () { - const operator = addrs[2]; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - await parent.connect(operator)["burn(uint256)"](parentId); - await checkBurntParent(); - }); - - it("can recursively burn nested token", async function () { - const childId1 = 99; - const granchildId = 999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1); - await child - .connect(tokenOwner) - .acceptChild(childId1, 0, child.address, granchildId); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - expect(await parent.childrenOf(parentId)).to.eql([ - [bn(childId1), child.address], - ]); - expect(await child.childrenOf(childId1)).to.eql([ - [bn(granchildId), child.address], - ]); - expect(await child.directOwnerOf(granchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - - // Sets recursive burns to 2 - await parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 2); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(0); - expect(await child.balanceOf(parent.address)).to.equal(0); - expect(await child.balanceOf(child.address)).to.equal(0); - - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - await expect( - parent.directOwnerOf(parentId) - ).to.be.revertedWithCustomError(parent, "ERC721InvalidTokenId"); - - await expect(child.ownerOf(childId1)).to.be.revertedWithCustomError( - child, - "ERC721InvalidTokenId" - ); - await expect(child.directOwnerOf(childId1)).to.be.revertedWithCustomError( - child, - "ERC721InvalidTokenId" - ); - - await expect(parent.ownerOf(granchildId)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - await expect( - parent.directOwnerOf(granchildId) - ).to.be.revertedWithCustomError(parent, "ERC721InvalidTokenId"); - }); - - it("can recursively burn nested token with the right number of recursive burns", async function () { - // Parent - // -> Child1 - // -> GrandChild1 - // -> GrandChild2 - // -> GreatGrandChild1 - // -> Child2 - // Total tree 5 (4 recursive burns) - const childId1 = 99; - const childId2 = 100; - const grandChild1 = 999; - const grandChild2 = 1000; - const greatGrandChild1 = 9999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - await child.nestMint(child.address, grandChild1, childId1); - await child.nestMint(child.address, grandChild2, childId1); - await child.nestMint(child.address, greatGrandChild1, grandChild2); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId2); - await child - .connect(tokenOwner) - .acceptChild(childId1, 0, child.address, grandChild1); - await child - .connect(tokenOwner) - .acceptChild(childId1, 0, child.address, grandChild2); - await child - .connect(tokenOwner) - .acceptChild(grandChild2, 0, child.address, greatGrandChild1); - - // 0 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 0) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, childId1); - // 1 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 1) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, grandChild1); - // 2 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 2) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, grandChild2); - // 3 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 3) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, greatGrandChild1); - // 4 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 4) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, childId2); - // 5 is just enough - await parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 5); - }); - - async function checkBurntParent() { - expect(await parent.balanceOf(addrs[1].address)).to.equal(0); - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - } - }); - - describe("Transferring Active Children", async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - childId1 = 99; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1); - }); - - it("can transfer child with to as root owner", async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - tokenOwner.address, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(); - }); - - it("can transfer child to another address", async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it("can transfer child to another NFT", async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - false, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, false); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(newParentId), - true, - ]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([ - [bn(childId1), child.address], - ]); - }); - - it("cannot transfer child out of index", async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - badIndex, - child.address, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "ChildIndexOutOfRange"); - }); - - it("cannot transfer child if address or id do not match", async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - otherAddress, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - otherChildId, - false, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - }); - - it("can transfer child if approved", async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child if approved for all", async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child with grandchild and children are ok", async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - }); - - it("cannot transfer child if not child root owner", async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent - .connect(notOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721NotApprovedOrOwner"); - }); - - it("cannot transfer child from not existing parent", async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - badChildId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721InvalidTokenId"); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - rootOwnerAddress, - bn(0), - false, - ]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe("Transferring Pending Children", async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it("can transfer child with to as root owner", async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - tokenOwner.address, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(); - }); - - it("can transfer child to another address", async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it("can transfer child to another NFT", async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - true, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, true); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(newParentId), - true, - ]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([ - [bn(childId1), child.address], - ]); - }); - - it("cannot transfer child out of index", async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - badIndex, - child.address, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "PendingChildIndexOutOfRange"); - }); - - it("cannot transfer child if address or id do not match", async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - otherAddress, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - otherChildId, - true, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - }); - - it("can transfer child if approved", async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child if approved for all", async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child with grandchild and children are ok", async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - }); - - it("cannot transfer child if not child root owner", async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent - .connect(notOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721NotApprovedOrOwner"); - }); - - it("cannot transfer child from not existing parent", async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - badChildId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721InvalidTokenId"); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - rootOwnerAddress, - bn(0), - false, - ]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe("Transfer", async function () { - it("can transfer token", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - // Balances and ownership are updated - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - }); - - it("cannot transfer not owned token", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(newOwner).transfer(newOwner.address, tokenId1) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("cannot transfer to address zero", async function () { - const firstOwner = addrs[1]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(firstOwner).transfer(ADDRESS_ZERO, tokenId1) - ).to.be.revertedWithCustomError(child, "ERC721TransferToTheZeroAddress"); - }); - - it("can transfer token from approved address (not owner)", async function () { - const firstOwner = addrs[1]; - const approved = addrs[2]; - const newOwner = addrs[3]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - - await parent.connect(firstOwner).approve(approved.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - }); - - it("can transfer not nested token with child to address and owners/children are ok", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await parent.connect(firstOwner).transfer(newOwner.address, parentId); - - // Balances and ownership are updated - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - - expect(await parent.ownerOf(parentId)).to.eql(newOwner.address); - expect(await parent.directOwnerOf(parentId)).to.eql([ - newOwner.address, - bn(0), - false, - ]); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(newOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(parentId), - true, - ]); - - // Parent still has its children - expect(await parent.pendingChildrenOf(parentId)).to.eql([ - [bn(childId1), child.address], - ]); - }); - - it("cannot directly transfer nested child", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.connect(firstOwner).transfer(newOwner.address, childId1) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("can transfer parent token to token with same owner, family tree is ok", async function () { - const firstOwner = addrs[1]; - const grandParentId = 999; - await parent.mint(firstOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(firstOwner.address)).to.equal(2); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Transfers token parentId to (parent.address, token grandParentId) - await parent - .connect(firstOwner) - .nestTransfer(parent.address, parentId, grandParentId); - - // Balances unchanged since root owner is the same - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - - it("can transfer parent token to token with different owner, family tree is ok", async function () { - const firstOwner = addrs[1]; - const otherOwner = addrs[2]; - const grandParentId = 999; - await parent.mint(otherOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // firstOwner calls parent to transfer parent token parent - await parent - .connect(firstOwner) - .nestTransfer(parent.address, parentId, grandParentId); - - // Balances update - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - }); - - describe("Nest Transfer", async function () { - let firstOwner: SignerWithAddress; - let parentId: number; - let childId1: number; - - beforeEach(async function () { - firstOwner = addrs[1]; - parentId = 1; - childId1 = 99; - await parent.mint(firstOwner.address, parentId); - await child.mint(firstOwner.address, childId1); - }); - - it("cannot nest tranfer from non immediate owner (owner of parent)", async function () { - const otherParentId = 2; - await parent.mint(firstOwner.address, otherParentId); - // We send it to the parent first - await child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, parentId); - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, otherParentId) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("cannot nest tranfer to same NFT", async function () { - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child - .connect(firstOwner) - .nestTransfer(child.address, childId1, childId1) - ).to.be.revertedWithCustomError(child, "NestableTransferToSelf"); - }); - - it("cannot nest tranfer a descendant same NFT", async function () { - // We can no longer nest transfer it, even if we are the root owner: - await child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, parentId); - const grandChildId = 999; - await child.nestMint(child.address, grandChildId, childId1); - // Ownership is now parent->child->granChild - // Cannot send parent to grandChild - await expect( - parent - .connect(firstOwner) - .nestTransfer(child.address, parentId, grandChildId) - ).to.be.revertedWithCustomError(child, "NestableTransferToDescendant"); - // Cannot send parent to child - await expect( - parent - .connect(firstOwner) - .nestTransfer(child.address, parentId, childId1) - ).to.be.revertedWithCustomError(child, "NestableTransferToDescendant"); - }); - - it("cannot nest tranfer if ancestors tree is too deep", async function () { - let lastId = childId1; - for (let i = 101; i <= 200; i++) { - await child.nestMint(child.address, i, lastId); - lastId = i; - } - // Ownership is now parent->child->child->child->child...->lastChild - // Cannot send parent to lastChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, lastId) - ).to.be.revertedWithCustomError(child, "NestableTooDeep"); - }); - - it("cannot nest tranfer if not owner", async function () { - const notOwner = addrs[3]; - await expect( - child.connect(notOwner).nestTransfer(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("cannot nest tranfer to address 0", async function () { - await expect( - child.connect(firstOwner).nestTransfer(ADDRESS_ZERO, childId1, parentId) - ).to.be.revertedWithCustomError(child, "ERC721TransferToTheZeroAddress"); - }); - - it("cannot nest tranfer to a non contract", async function () { - const newOwner = addrs[2]; - await expect( - child - .connect(firstOwner) - .nestTransfer(newOwner.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "IsNotContract"); - }); - - it("cannot nest tranfer to contract if it does implement INestable", async function () { - const ERC721 = await ethers.getContractFactory("ERC721Mock"); - const nonNestable = await ERC721.deploy("Non receiver", "NR"); - await nonNestable.deployed(); - await expect( - child - .connect(firstOwner) - .nestTransfer(nonNestable.address, childId1, parentId) - ).to.be.revertedWithCustomError( - child, - "NestableTransferToNonNestableImplementer" - ); - }); - - it("can nest tranfer to INestable contract", async function () { - await child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, parentId); - expect(await child.ownerOf(childId1)).to.eql(firstOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(parentId), - true, - ]); - }); - - it("cannot nest tranfer to non existing parent token", async function () { - const notExistingParentId = 9999; - await expect( - child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, notExistingParentId) - ).to.be.revertedWithCustomError(parent, "ERC721InvalidTokenId"); - }); - }); - - async function checkNoChildrenNorPending(parentId: number): Promise { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([]); - } - - async function checkAcceptedAndPendingChildren( - contract: EquippableTokenMock, - tokenId1: number, - expectedAccepted: any[], - expectedPending: any[] - ) { - const accepted = await contract.childrenOf(tokenId1); - expect(accepted).to.eql(expectedAccepted); - - const pending = await contract.pendingChildrenOf(tokenId1); - expect(pending).to.eql(expectedPending); - } -}); diff --git a/assets/eip-6220/test/renderUtils.ts b/assets/eip-6220/test/renderUtils.ts deleted file mode 100644 index d946d09..0000000 --- a/assets/eip-6220/test/renderUtils.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { - EquippableTokenMock, - EquipRenderUtils, - MultiAssetRenderUtils, -} from "../typechain-types"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -async function assetsFixture() { - const equipFactory = await ethers.getContractFactory("EquippableTokenMock"); - const renderUtilsFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - const renderUtilsEquipFactory = await ethers.getContractFactory( - "EquipRenderUtils" - ); - - const equip = await equipFactory.deploy(); - await equip.deployed(); - - const renderUtils = await renderUtilsFactory.deploy(); - await renderUtils.deployed(); - - const renderUtilsEquip = ( - await renderUtilsEquipFactory.deploy() - ); - await renderUtilsEquip.deployed(); - - return { equip, renderUtils, renderUtilsEquip }; -} - -describe("Render Utils", async function () { - let owner: SignerWithAddress; - let someCatalog: SignerWithAddress; - let equip: EquippableTokenMock; - let renderUtils: MultiAssetRenderUtils; - let renderUtilsEquip: EquipRenderUtils; - let tokenId: number; - - const resId = bn(1); - const resId2 = bn(2); - const resId3 = bn(3); - const resId4 = bn(4); - - before(async function () { - ({ equip, renderUtils, renderUtilsEquip } = await loadFixture( - assetsFixture - )); - - const signers = await ethers.getSigners(); - owner = signers[0]; - someCatalog = signers[1]; - tokenId = 1; - - await equip.mint(owner.address, tokenId); - await equip.addEquippableAssetEntry( - resId, - 0, - ethers.constants.AddressZero, - "ipfs://res1.jpg", - [] - ); - await equip.addEquippableAssetEntry( - resId2, - 1, - someCatalog.address, - "ipfs://res2.jpg", - [1, 3, 4] - ); - await equip.addEquippableAssetEntry( - resId3, - 0, - ethers.constants.AddressZero, - "ipfs://res3.jpg", - [] - ); - await equip.addEquippableAssetEntry( - resId4, - 2, - someCatalog.address, - "ipfs://res4.jpg", - [4] - ); - await equip.addAssetToToken(tokenId, resId, 0); - await equip.addAssetToToken(tokenId, resId2, 0); - await equip.addAssetToToken(tokenId, resId3, resId); - await equip.addAssetToToken(tokenId, resId4, 0); - - await equip.acceptAsset(tokenId, 0, resId); - await equip.acceptAsset(tokenId, 1, resId2); - await equip.setPriority(tokenId, [10, 5]); - }); - - describe("Render Utils MultiAsset", async function () { - it("can get active assets", async function () { - expect(await renderUtils.getActiveAssets(equip.address, tokenId)).to.eql([ - [resId, 10, "ipfs://res1.jpg"], - [resId2, 5, "ipfs://res2.jpg"], - ]); - }); - - it("can get assets by id", async function () { - expect( - await renderUtils.getAssetsById(equip.address, tokenId, [resId, resId2]) - ).to.eql(["ipfs://res1.jpg", "ipfs://res2.jpg"]); - }); - - it("can get pending assets", async function () { - expect(await renderUtils.getPendingAssets(equip.address, tokenId)).to.eql( - [ - [resId4, bn(0), bn(0), "ipfs://res4.jpg"], - [resId3, bn(1), resId, "ipfs://res3.jpg"], - ] - ); - }); - - it("can get top asset by priority", async function () { - expect( - await renderUtils.getTopAssetMetaForToken(equip.address, tokenId) - ).to.eql("ipfs://res2.jpg"); - }); - - it("cannot get top asset if token has no assets", async function () { - const otherTokenId = 2; - await equip.mint(owner.address, otherTokenId); - await expect( - renderUtils.getTopAssetMetaForToken(equip.address, otherTokenId) - ).to.be.revertedWithCustomError(renderUtils, "TokenHasNoAssets"); - }); - }); - - describe("Render Utils Equip", async function () { - it("can get active assets", async function () { - expect( - await renderUtilsEquip.getExtendedActiveAssets(equip.address, tokenId) - ).to.eql([ - [resId, bn(0), 10, ethers.constants.AddressZero, "ipfs://res1.jpg", []], - [ - resId2, - bn(1), - 5, - someCatalog.address, - "ipfs://res2.jpg", - [bn(1), bn(3), bn(4)], - ], - ]); - }); - - it("can get pending assets", async function () { - expect( - await renderUtilsEquip.getExtendedPendingAssets(equip.address, tokenId) - ).to.eql([ - [ - resId4, - bn(2), - bn(0), - bn(0), - someCatalog.address, - "ipfs://res4.jpg", - [bn(4)], - ], - [ - resId3, - bn(0), - bn(1), - resId, - ethers.constants.AddressZero, - "ipfs://res3.jpg", - [], - ], - ]); - }); - }); -}); diff --git a/assets/eip-6353/contracts/CharityToken.sol b/assets/eip-6353/contracts/CharityToken.sol deleted file mode 100644 index e7834da..0000000 --- a/assets/eip-6353/contracts/CharityToken.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC20Charity.sol"; - -/** -*@title ERC720 charity Token -*@dev Extension of ERC720 Token that can be partially donated to a charity project -* -*This extensions keeps track of donations to charity addresses. The whitelisted adress are from a another contract (Reserve) - */ - -contract CharityToken is ERC20Charity{ - constructor() ERC20("TestToken", "TST") { - _mint(msg.sender, 10000 * 10 ** decimals()); - } - - /** @dev Creates `amount` tokens and assigns them to `to`, increasing - * the total supply. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - * @param to The address to assign the amount to. - * @param amount The amount of token to mint. - */ - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } - - function selfmint() public { - _mint(msg.sender, 100 * 10 ** decimals()); - } - - - //Test support for ERC-Charity - bytes4 private constant _INTERFACE_ID_ERC_CHARITY = type(IERC20charity).interfaceId; // 0x557512b6 - //bytes4 private constant _INTERFACE_ID_ERCcharity =type(IERC165).interfaceId; // ERC165S - function checkInterface(address testContract) external view returns (bool) { - (bool success) = IERC165(testContract).supportsInterface(_INTERFACE_ID_ERC_CHARITY); - return success; - } - - /*function InterfaceId() external returns (bytes4) { - bytes4 _INTERFACE_ID = type(IERC20charity).interfaceId; - return _INTERFACE_ID ; - }*/ - -} diff --git a/assets/eip-6353/contracts/ERC20Charity.sol b/assets/eip-6353/contracts/ERC20Charity.sol deleted file mode 100644 index 73d2c05..0000000 --- a/assets/eip-6353/contracts/ERC20Charity.sol +++ /dev/null @@ -1,328 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./interfaces/IERC20charity.sol"; - -/** - *@title ERC720 charity Token - *@author Aubay - *@dev Extension of ERC720 Token that can be partially donated to a charity project - * - *This extensions keeps track of donations to charity addresses. The owner can chose the charity adresses listed. - *Users can active the donation option or not and specify a different pourcentage than the default one donate. - * A pourcentage af the amount of token transfered will be added and send to a charity address. - */ - -abstract contract ERC20Charity is IERC20charity, ERC20, Ownable { - mapping(address => uint256) public whitelistedRate; //Keep track of the rate for each charity address - mapping(address => uint256) internal indexOfAddresses; - mapping(address => mapping(address => uint256)) private _donation; //Keep track of the desired rate to donate for each user - mapping(address => address) private _defaultAddress; //keep track of each user's default charity address - - address[] whitelistedAddresses; //Addresses whitelisted - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165) returns (bool) { - return - interfaceId == type(IERC20charity).interfaceId || - interfaceId == type(IERC165).interfaceId; - } - - /** - *@dev The default rate of donation can be override - */ - function _defaultRate() internal pure virtual returns (uint256) { - return 10; // 0.1% - } - - /** - *@dev The denominator to interpret the rate of donation , defaults to 10000 so rate are expressed in basis points, but may be customized by an override. - * base 10000 , so 10000 =100% , 0 = 0% , 2000 =20% - */ - function _feeDenominator() internal pure virtual returns (uint256) { - return 10000; - } - - /** - *@notice Add address to whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toAdd` cannot be the zero address. - * - * @param toAdd The address to whitelist. - */ - function addToWhitelist(address toAdd) external virtual onlyOwner { - if (indexOfAddresses[toAdd] == 0) { - whitelistedRate[toAdd] = _defaultRate(); - whitelistedAddresses.push(toAdd); - indexOfAddresses[toAdd] = whitelistedAddresses.length; - } - - emit AddedToWhitelist(toAdd); - } - - /** - *@notice Remove the address from the whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toRemove` cannot be the zero address. - * - * @param toRemove The address to remove from whitelist. - */ - function deleteFromWhitelist(address toRemove) external virtual onlyOwner { - uint256 index1 = indexOfAddresses[toRemove]; - require(index1 > 0, "Invalid index"); //Indexing starts at 1, 0 is not allowed - // move the last item into the index being vacated - address lastValue = whitelistedAddresses[ - whitelistedAddresses.length - 1 - ]; - whitelistedAddresses[index1 - 1] = lastValue; // adjust for 1-based indexing - indexOfAddresses[lastValue] = index1; - whitelistedAddresses.pop(); - indexOfAddresses[toRemove] = 0; - - delete whitelistedRate[toRemove]; //whitelistedRate[toRemove] =0; - emit RemovedFromWhitelist(toRemove); - } - - /// @notice Get all registered charity addresses - /// @return List of all registered donations addresses - function getAllWhitelistedAddresses() external view returns (address[] memory) { - return whitelistedAddresses; - } - - /// @notice Display for a user the rate of the default charity address that will receive donation. - /// @return The default rate of the registered address for the user. - function getRate() external view returns (uint256) { - return _donation[msg.sender][_defaultAddress[msg.sender]]; - } - - /** - *@notice Set for a user a default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - * @param whitelistedAddr The address to set as default. - */ - function setSpecificDefaultAddress( - address whitelistedAddr - ) external virtual { - require( - whitelistedRate[whitelistedAddr] != 0, - "ERC20Charity: invalid whitelisted rate" - ); - _defaultAddress[msg.sender] = whitelistedAddr; - _donation[msg.sender][whitelistedAddr] = whitelistedRate[ - whitelistedAddr - ]; - emit DonnationAddressChanged(whitelistedAddr); - } - - /** - *@notice Set for a user a default charity address that will receive donation. - * The rate is specified by the user. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate - * or to the rate specified by the owner of this contract in {whitelistedRate}. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificDefaultAddressAndRate( - address whitelistedAddr, - uint256 rate - ) external virtual { - require( - rate <= _feeDenominator(), - "ERC20Charity: rate must be between 0 and _feeDenominator" - ); - require( - rate >= _defaultRate(), - "ERC20Charity: rate fee must exceed default rate" - ); - require( - rate >= whitelistedRate[whitelistedAddr], - "ERC20Charity: rate fee must exceed the fee set by the owner" - ); - require( - whitelistedRate[whitelistedAddr] != 0, - "ERC20Charity: invalid whitelisted address" - ); - _defaultAddress[msg.sender] = whitelistedAddr; - _donation[msg.sender][whitelistedAddr] = rate; - emit DonnationAddressAndRateChanged(whitelistedAddr, rate); - } - - /** - *@notice Set personlised rate for charity address in {whitelistedRate}. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificRate( - address whitelistedAddr, - uint256 rate - ) external virtual onlyOwner { - require( - rate <= _feeDenominator(), - "ERC20Charity: rate must be between 0 and _feeDenominator" - ); - require( - rate >= _defaultRate(), - "ERC20Charity: rate fee must exceed default rate" - ); - require( - whitelistedRate[whitelistedAddr] != 0, - "ERC20Charity: invalid whitelisted address" - ); - whitelistedRate[whitelistedAddr] = rate; - emit ModifiedCharityRate(whitelistedAddr, rate); - } - - /** - *@notice Display for a user the default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - */ - function specificDefaultAddress() external view virtual returns (address) { - return _defaultAddress[msg.sender]; - } - - /** - * inherit IERC20charity - */ - function charityInfo( - address charityAddr - ) external view virtual returns (bool, uint256 rate) { - rate = whitelistedRate[charityAddr]; - if (rate != 0) { - return (true, rate); - } else { - return (false, rate); - } - } - - /** - *@notice Delete The Default Address and so deactivate donnations . - */ - function deleteDefaultAddress() external virtual { - _defaultAddress[msg.sender] = address(0); - emit DonnationAddressChanged(address(0)); - } - - /** - *@notice Return the rate to donate. - * @dev Requirements: - * - * - `from` cannot be the zero address - * - * @param from The address to get rate of donation. - */ - function _returnRate(address from) internal virtual returns (uint256 rate) { - address whitelistedAddr = _defaultAddress[from]; - rate = _donation[from][whitelistedAddr]; - if ( - whitelistedRate[whitelistedAddr] == 0 || - _defaultAddress[from] == address(0) - ) { - rate = 0; - } - return rate; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer( - address to, - uint256 amount - ) public virtual override returns (bool) { - address owner = _msgSender(); - - if (_defaultAddress[msg.sender] != address(0)) { - address whitelistedAddr = _defaultAddress[msg.sender]; - uint256 rate = _returnRate(msg.sender); - uint256 donate = (amount * rate) / _feeDenominator(); - _transfer(owner, whitelistedAddr, donate); - } - _transfer(owner, to, amount); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - - if (_defaultAddress[from] != address(0)) { - address whitelistedAddr = _defaultAddress[from]; - uint256 rate = _returnRate(from); - uint256 donate = (amount * rate) / _feeDenominator(); - _spendAllowance(from, spender, donate); - _transfer(from, whitelistedAddr, donate); - } - _transfer(from, to, amount); - return true; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve( - address spender, - uint256 amount - ) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - if (_defaultAddress[msg.sender] != address(0)) { - uint256 rate = _returnRate(msg.sender); - uint256 donate = (amount * rate) / _feeDenominator(); - _approve(owner, spender, (donate + amount)); - } - return true; - } -} diff --git a/assets/eip-6353/contracts/graphs/charity graph4.svg b/assets/eip-6353/contracts/graphs/charity graph4.svg deleted file mode 100644 index abdc319..0000000 --- a/assets/eip-6353/contracts/graphs/charity graph4.svg +++ /dev/null @@ -1,378 +0,0 @@ - - - - - - - - -ERC20Charity - - - - -Legend - - - - - -supportsInterface - - - - - -type - - - - - - - - - - - - - - - - - -_defaultRate - - - - - -_feeDenominator - - - - - -addToWhitelist - - - - - - - - - - - -AddedToWhitelist - - - - - - - - - - - -deleteFromWhitelist - - - - - -RemovedFromWhitelist - - - - - - - - - - - -setSpecificDefaultAddress - - - - - -DonnationAddressChanged - - - - - - - - - - - -setSpecificDefaultAddressAndRate - - - - - - - - - - - - - - - - - -DonnationAddressAndRateChanged - - - - - - - - - - - -setSpecificRate - - - - - - - - - - - - - - - - - -ModifiedCharityRate - - - - - - - - - - - -SpecificDefaultAddress - - - - - -charityInfo - - - - - -DeleteDefaultAddress - - - - - - - - - - - -_returnRate - - - - - -transfer - - - - - - - - - - - - - - - - - -_msgSender - - - - - - - - - - - -_transfer - - - - - - - - - - - - - - - - - -transferFrom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -_spendAllowance - - - - - - - - - - - - - - - - - -approve - - - - - - - - - - - - - - - - - - - - - - - -_approve - - - - - - - - - - - - - - - - - -Internal Call -External Call -Defined Contract -Undefined Contract - - - - - -    -    - -    - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/eip-6353/contracts/graphs/test graph3.svg b/assets/eip-6353/contracts/graphs/test graph3.svg deleted file mode 100644 index b9cc579..0000000 --- a/assets/eip-6353/contracts/graphs/test graph3.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - -CharityToken - - - - -Legend - - - - - -<Constructor> - - - - - -_mint - - - - - - - - - - - -decimals - - - - - - - - - - - -mint - - - - - - - - - - - -checkInterface - - - - - -IERC165 - - - - - - - - - - - -Internal Call -External Call -Defined Contract -Undefined Contract - - - - - -    -    - -    - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/eip-6353/contracts/interfaces/IERC20charity.sol b/assets/eip-6353/contracts/interfaces/IERC20charity.sol deleted file mode 100644 index 928b78a..0000000 --- a/assets/eip-6353/contracts/interfaces/IERC20charity.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -//import "./IERC165.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -/// -/// @dev Required interface of an ERC20 Charity compliant contract. -/// -interface IERC20charity is IERC165 { - /// ERC165 bytes to add to interface array - set in parent contract - /// implementing this standard - /// - ///type(IERC20charity).interfaceId.interfaceId == 0x557512b6 - /// bytes4 private constant _INTERFACE_ID_ERCcharity = 0x557512b6; - /// _registerInterface(_INTERFACE_ID_ERCcharity); - - - /** - * @dev Emitted when `toAdd` charity address is added to `whitelistedRate`. - */ - event AddedToWhitelist (address toAdd); - - /** - * @dev Emitted when `toRemove` charity address is deleted from `whitelistedRate`. - */ - event RemovedFromWhitelist (address toRemove); - - /** - * @dev Emitted when `_defaultAddress` charity address is modified and set to `whitelistedAddr`. - */ - event DonnationAddressChanged (address whitelistedAddr); - - /** - * @dev Emitted when `_defaultAddress` charity address is modified and set to `whitelistedAddr` - * and _donation is set to `rate`. - */ - event DonnationAddressAndRateChanged (address whitelistedAddr,uint256 rate); - - /** - * @dev Emitted when `whitelistedRate` for `whitelistedAddr` is modified and set to `rate`. - */ - event ModifiedCharityRate(address whitelistedAddr,uint256 rate); - - /** - *@notice Called with the charity address to determine if the contract whitelisted the address - *and if it is the rate assigned. - *@param addr - the Charity address queried for donnation information. - *@return whitelisted - true if the contract whitelisted the address to receive donnation - *@return defaultRate - the rate defined by the contract owner by default , the minimum rate allowed different from 0 - */ - function charityInfo( - address addr - ) external view returns ( - bool whitelisted, - uint256 defaultRate - ); - - /** - *@notice Add address to whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toAdd` cannot be the zero address. - * - * @param toAdd The address to whitelist. - */ - function addToWhitelist(address toAdd) external; - - /** - *@notice Remove the address from the whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toRemove` cannot be the zero address. - * - * @param toRemove The address to remove from whitelist. - */ - function deleteFromWhitelist(address toRemove) external; - - /** - *@notice Get all registered charity addresses. - */ - function getAllWhitelistedAddresses() external view returns (address[] memory) ; - - /** - *@notice Display for a user the rate of the default charity address that will receive donation. - */ - function getRate() external view returns (uint256); - - /** - *@notice Set personlised rate for charity address in {whitelistedRate}. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificRate(address whitelistedAddr , uint256 rate) external; - - /** - *@notice Set for a user a default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - * @param whitelistedAddr The address to set as default. - */ - function setSpecificDefaultAddress(address whitelistedAddr) external; - - /** - *@notice Set for a user a default charity address that will receive donation. - * The rate is specified by the user. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate - * or to the rate specified by the owner of this contract in {whitelistedRate}. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificDefaultAddressAndRate(address whitelistedAddr , uint256 rate) external; - - /** - *@notice Display for a user the default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - */ - function specificDefaultAddress() external view returns ( - address defaultAddress - ); - - /** - *@notice Delete The Default Address and so deactivate donnations . - */ - function deleteDefaultAddress() external; -} \ No newline at end of file diff --git a/assets/eip-6353/test/.gitkeep b/assets/eip-6353/test/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/eip-6353/test/charity.js b/assets/eip-6353/test/charity.js deleted file mode 100644 index 01f80f6..0000000 --- a/assets/eip-6353/test/charity.js +++ /dev/null @@ -1,173 +0,0 @@ -const { expect } = require("chai"); -const { ethers } = require("hardhat"); - -describe('Compile: Test charity donation configuration ', function() { - before(async function() { - [owner, user, charity1, charity2, charity3, user2]= await ethers.getSigners(); - }); - it("Deploy contract ", async function() { - charityTokenContract = await ethers.getContractFactory("CharityToken"); - charity = await charityTokenContract.deploy(); - decimals = await charity.decimals(); - - console.log("CharityToken Contract : ", await charity.address); - console.log("Owner : ", owner.address); - console.log("User : ", user.address); - console.log("User2 : ", user2.address); - console.log("Charity 1 : ", charity1.address); - console.log("Charity 2 : ", charity2.address); - console.log("Charity 3 : ", charity3.address); - }); - - it("Owner: Whitelist a charity address ", async function () { - const amount_send= 10000; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.mint(user.address,amnt.toString() ); - - await charity.addToWhitelist(charity1.address); - - //console.log((await charity.whitelistedRate(charity1)).toString()); - - expect(await charity.whitelistedRate(charity1.address)).to.equal( 10, "Failed to store defaultRate"); - - await charity.addToWhitelist(charity2.address); - }); - - it("Owner: custom rate for charity address ", async function () { - - await charity.setSpecificRate(charity2.address,200); - expect(await charity.whitelistedRate(charity2.address)).to.equal( 200, "Failed to custom ate"); - }); - - it("Fails: User Whitelist a charity address ", async function () { - await expect(charity.connect(user).addToWhitelist(charity1.address)).to.be.revertedWith( "Ownable: caller is not the owner"); - }); - - it("User: custom rate for default charity ", async function () { - expect(await charity.connect(user).specificDefaultAddress()).to.equal('0x0000000000000000000000000000000000000000',"The address isn't set yet it should be 0x0000000000000000000000000000000000000000 "); - //console.log("default charity adress" , await charity.connect(user).specificDefaultAddress()); - //set - await charity.connect(user).setSpecificDefaultAddressAndRate(charity1.address,20); //rate is set to 2% for charity1 - expect(await charity.connect(user).specificDefaultAddress()).to.equal(charity1.address,"The address isn't set to charity1 "); - //console.log("default charity adress" , await charity.connect(user).specificDefaultAddress()); - }); - - it("Fails: User Whitelist a charity address that is not whitelisted ", async function () { - await expect(charity.connect(user).setSpecificDefaultAddressAndRate(charity3.address,20)).to.be.revertedWith( "ERC20Charity: invalid whitelisted address"); - }); - - it("Fails: User Whitelist a charity address with an insufficient rate", async function () { - await expect(charity.connect(user).setSpecificDefaultAddressAndRate(charity1.address,5)).to.be.revertedWith( "ERC20Charity: rate fee must exceed default rate"); - await expect(charity.connect(user).setSpecificDefaultAddressAndRate(charity2.address,100)).to.be.revertedWith( "ERC20Charity: rate fee must exceed the fee set by the owner"); - }); - - it("User: transfer an amount to charity when token is transferred", async function () { - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).transfer(user2.address,amnt); //default address is charity1 with 2% rate - - console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.2, "Failed : charity balance should be increased to 0.2"); - - }); - - it("User: transfer (from) an amount to charity when token is transferred", async function () { - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).approve(owner.address,amnt); - await charity.connect( owner).transferFrom(user.address,user2.address,amnt); - - console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.4, "Failed : charity balance should be increased to 0.4"); - }); - - it("User: User deactivate/activate donation", async function () { - //deactivate donnation - await charity.connect(user).deleteDefaultAddress(); - expect(await charity.connect(user).specificDefaultAddress()).to.equal( '0x0000000000000000000000000000000000000000', "Failed : address shloud be null"); - - //try to transfer now - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).transfer(user2.address,amnt); - - //console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - //console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - //console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - //the default address of user1 is no longer whitelisted , the donation shouldn't happen. - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.4, "Failed : charity balance should be of 0.4"); - - //activate donnation and transfer - await charity.connect(user).setSpecificDefaultAddressAndRate(charity1.address,20); //rate is reset to 2% for charity1 - console.log("custom rate changed", (await charity.connect(user).getRate()).toString()); - await charity.connect(user).transfer(user2.address,amnt); - - //console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - //console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - //console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.6, "Failed : charity balance should be of 0.6"); - - }); - - it("Owner: delete charity ", async function () { - await charity.deleteFromWhitelist(charity1.address); - //console.log("Charity 1 rate: "(await charity.whitelistedRate.call(charity1)).toString()); - expect(await charity.whitelistedRate(charity1.address)).to.equal(0, "Failed to delete defaultRate for charity"); - - //try to transfer now - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).transfer(user2.address,amnt); - - console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - //the default address of user1 is no longer whitelisted , the donation shouldn't happen. - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.6, "Failed : charity balance should be of 0.6"); - }); - - it("Interface test ", async function () { - // const support = await charity.checkInterface.call(charity.address); - const support = await charity.callStatic.checkInterface(charity.address); // to correct - //console.log(typeof support); - console.log(support); - expect(support).to.equal( true); - - // see if the charity address is whitelisted - const info1 = await charity.charityInfo(charity1.address); - const info2 = await charity.charityInfo(charity2.address); - - console.log("charity 1: ",info1[0],info1[1].toString()); - - expect(info1[0]).to.equal( false, "Failed : charity should'nt be whitelisted"); - expect(info1[1]).to.equal( 0, "Failed : charity rate should be null"); - - console.log("charity 2: ",info2[0],info2[1].toString()); - - expect(info2[0]).to.equal( true, "Failed : charity should be whitelisted"); - expect(info2[1]).to.equal( 200, "Failed : charity rate should be set to 200 (2%)"); - - }); - - it("Charity list (add/delete) test ", async function () { - await charity.addToWhitelist(charity1.address); - await charity.addToWhitelist(charity3.address); - - listAddr = await charity.getAllWhitelistedAddresses(); - console.log(listAddr); - - await charity.deleteFromWhitelist(charity1.address); - console.log( await charity.getAllWhitelistedAddresses()); - - await charity.deleteFromWhitelist(charity2.address); - console.log( await charity.getAllWhitelistedAddresses()); - - await charity.deleteFromWhitelist(charity3.address); - console.log( await charity.getAllWhitelistedAddresses()); - }); -}); diff --git a/assets/eip-6358/img/o-dlt.png b/assets/eip-6358/img/o-dlt.png deleted file mode 100644 index 58bc839..0000000 Binary files a/assets/eip-6358/img/o-dlt.png and /dev/null differ diff --git a/assets/eip-6358/src/.gitignore b/assets/eip-6358/src/.gitignore deleted file mode 100644 index bc00b7b..0000000 --- a/assets/eip-6358/src/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -build -.secret - -package-lock.json -truffle-config.js diff --git a/assets/eip-6358/src/README.md b/assets/eip-6358/src/README.md deleted file mode 100644 index 54a4487..0000000 --- a/assets/eip-6358/src/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Example implementation of EIP-6358 - -## Prerequisites -- truffle >= v5.7.9 -- node >= v18.12.1 -- npm >= 8.19.2 -- npx >= 8.19.2 - -## Installation -``` -npm install -``` - -Add the configuration file `truffle-config.js` into the directory `./`. The file `truffle-config.js` can be generated by executing the command in an $empty directory$: -``` -npx truffle init -``` - -**Note that:** - -- type `N` when asked `Overwrite contracts?` -- type `N` when asked `Overwrite migrations?` -- type `N` when asked `Overwrite test?` - -After `truffle-config.js` is generated, then: - -- Uncommnet the content of `development`, like this: - -``` -development: { - host: "127.0.0.1", // Localhost (default: none) - port: 8545, // Standard Ethereum port (default: none) - network_id: "*", // Any network (default: none) - }, -``` - -## Compilation -``` -touch .secret -npx truffle compile -``` - -## Unit test -### Launch local testnet -``` -npx ganache -s 0 -``` - -### Test -Open another terminate - -``` -npx truffle test -``` \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/ERC20.sol b/assets/eip-6358/src/contracts/ERC20.sol deleted file mode 100644 index 7eaa2d4..0000000 --- a/assets/eip-6358/src/contracts/ERC20.sol +++ /dev/null @@ -1,383 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; - -/** - * @dev Implementation of the {IERC20} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * For a generic mechanism see {ERC20PresetMinterPauser}. - * - * TIP: For a detailed writeup see our guide - * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * We have followed general OpenZeppelin Contracts guidelines: functions revert - * instead returning `false` on failure. This behavior is nonetheless - * conventional and does not conflict with the expectations of ERC20 - * applications. - * - * Additionally, an {Approval} event is emitted on calls to {transferFrom}. - * This allows applications to reconstruct the allowance for all accounts just - * by listening to said events. Other implementations of the EIP may not emit - * these events, as it isn't required by the specification. - * - * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} - * functions have been added to mitigate the well-known issues around setting - * allowances. See {IERC20-approve}. - */ -contract ERC20 is Context, IERC20, IERC20Metadata { - mapping(address => uint256) internal _balances; - - mapping(address => mapping(address => uint256)) internal _allowances; - - uint256 internal _totalSupply; - - string private _name; - string private _symbol; - - /** - * @dev Sets the values for {name} and {symbol}. - * - * The default value of {decimals} is 18. To select a different value for - * {decimals} you should overload it. - * - * All two of these values are immutable: they can only be set once during - * construction. - */ - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the value {ERC20} uses, unless this function is - * overridden; - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual override returns (uint8) { - return 18; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account]; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, amount); - return true; - } - - /** - * @dev See {IERC20-allowance}. - */ - function allowance(address owner, address spender) public view virtual override returns (uint256) { - return _allowances[owner][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, allowance(owner, spender) + addedValue); - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `subtractedValue`. - */ - function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { - address owner = _msgSender(); - uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /** - * @dev Moves `amount` of tokens from `from` to `to`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - } - _balances[to] += amount; - - emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, amount); - } - - /** @dev Creates `amount` tokens and assigns them to `account`, increasing - * the total supply. - * - * Emits a {Transfer} event with `from` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - */ - function _mint(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: mint to the zero address"); - - _beforeTokenTransfer(address(0), account, amount); - - _totalSupply += amount; - _balances[account] += amount; - emit Transfer(address(0), account, amount); - - _afterTokenTransfer(address(0), account, amount); - } - - /** - * @dev Destroys `amount` tokens from `account`, reducing the - * total supply. - * - * Emits a {Transfer} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have at least `amount` tokens. - */ - function _burn(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: burn from the zero address"); - - _beforeTokenTransfer(account, address(0), amount); - - uint256 accountBalance = _balances[account]; - require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); - unchecked { - _balances[account] = accountBalance - amount; - } - _totalSupply -= amount; - - emit Transfer(account, address(0), amount); - - _afterTokenTransfer(account, address(0), amount); - } - - /** - * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Updates `owner` s allowance for `spender` based on spent `amount`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - /** - * @dev Hook that is called before any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} - - /** - * @dev Hook that is called after any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * has been transferred to `to`. - * - when `from` is zero, `amount` tokens have been minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens have been burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} -} diff --git a/assets/eip-6358/src/contracts/ERC6358FungibleExample.sol b/assets/eip-6358/src/contracts/ERC6358FungibleExample.sol deleted file mode 100644 index 5b3b98b..0000000 --- a/assets/eip-6358/src/contracts/ERC6358FungibleExample.sol +++ /dev/null @@ -1,387 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./ERC20.sol"; -import "./libraries/OmniverseProtocolHelper.sol"; -import "./interfaces/IERC6358Fungible.sol"; - -/** -* @notice Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded -* -* @member op: The operation type -* NOTE op: 0-31 are reserved values, 32-255 are custom values -* op: 0 - omniverse account `from` transfers `amount` tokens to omniverse account `exData`, `from` have at least `amount` tokens -* op: 1 - omniverse account `from` mints `amount` tokens to omniverse account `exData` -* op: 2 - omniverse account `from` burns `amount` tokens from his own, `from` have at least `amount` tokens -* @member exData: The operation data. This sector could be empty and is determined by `op`. For example: - when `op` is 0 and 1, `exData` stores the omniverse account that receives. - when `op` is 2, `exData` is empty. -* @member amount: The amount of tokens being operated - */ -struct Fungible { - uint8 op; - bytes exData; - uint256 amount; -} - -/** - * @notice Implementation of the {IERC6358Fungible} interface - */ -contract ERC6358FungibleExample is ERC20, Ownable, IERC6358Fungible { - uint8 constant TRANSFER = 0; - uint8 constant MINT = 1; - uint8 constant BURN = 2; - - /** @notice Used to index a delayed transaction - * sender: The account which sent the transaction - * nonce: The nonce of the delayed transaction - */ - struct DelayedTx { - bytes sender; - uint256 nonce; - } - - /** - * @notice The member information - * chainId: The chain which the member belongs to - * contractAddr: The contract address on the member chain - */ - struct Member { - uint32 chainId; - bytes contractAddr; - } - - // Chain id used to distinguish different chains - uint32 chainId; - // O-transaction cooling down time - uint256 public cdTime; - // Omniverse accounts record - mapping(bytes => RecordedCertificate) transactionRecorder; - // Transactions to be executed - mapping(bytes => OmniverseTx) public transactionCache; - - // All information of chains on which the token is deployed - Member[] members; - // Omniverse balances - mapping(bytes => uint256) omniverseBalances; - // Delay-executing transactions - DelayedTx[] delayedTxs; - // Account map from evm address to public key - mapping(address => bytes) accountsMap; - - event OmniverseTokenTransfer(bytes from, bytes to, uint256 value); - - /** - * @notice Initiates the contract - * @param _chainId The chain which the contract is deployed on - * @param _name The name of the token - * @param _symbol The symbol of the token - */ - constructor(uint32 _chainId, string memory _name, string memory _symbol) ERC20(_name, _symbol) { - chainId = _chainId; - } - - /** - * @notice See {IERC6358Fungible-sendOmniverseTransaction} - * Send an omniverse transaction - */ - function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external override { - _omniverseTransaction(_data); - } - - /** - * @notice See {IERC6358Fungible-triggerExecution} - */ - function triggerExecution() external { - require(delayedTxs.length > 0, "No delayed tx"); - - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - require(cache.timestamp != 0, "Not cached"); - require(cache.txData.nonce == delayedTxs[0].nonce, "Nonce error"); - (ERC6358TransactionData storage txData, uint256 timestamp) = (cache.txData, cache.timestamp); - require(block.timestamp >= timestamp + cdTime, "Not executable"); - delayedTxs[0] = delayedTxs[delayedTxs.length - 1]; - delayedTxs.pop(); - cache.timestamp = 0; - // Add to transaction recorder - RecordedCertificate storage rc = transactionRecorder[txData.from]; - rc.txList.push(cache); - - Fungible memory fungible = decodeData(txData.payload); - if (fungible.op == TRANSFER) { - _omniverseTransfer(txData.from, fungible.exData, fungible.amount); - } - else if (fungible.op == MINT) { - _checkOwner(txData.from); - _omniverseMint(fungible.exData, fungible.amount); - } - else if (fungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(fungible.exData, fungible.amount); - _omniverseBurn(fungible.exData, fungible.amount); - } - emit TransactionExecuted(txData.from, txData.nonce); - } - - /** - * @notice Check if the transaction can be executed successfully - */ - function _checkExecution(ERC6358TransactionData memory txData) internal view { - Fungible memory fungible = decodeData(txData.payload); - if (fungible.op == TRANSFER) { - _checkOmniverseTransfer(txData.from, fungible.amount); - } - else if (fungible.op == MINT) { - _checkOwner(txData.from); - } - else if (fungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(fungible.exData, fungible.amount); - } - else { - revert("OP code error"); - } - } - - /** - * @notice Returns the nearest exexutable delayed transaction info - * or returns default if not found - */ - function getExecutableDelayedTx() external view returns (DelayedTx memory ret) { - if (delayedTxs.length > 0) { - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - if (block.timestamp >= cache.timestamp + cdTime) { - ret = delayedTxs[0]; - } - } - } - - /** - * @notice Returns the count of delayed transactions - */ - function getDelayedTxCount() external view returns (uint256) { - return delayedTxs.length; - } - - /** - * @notice See {IERC6358Fungible-omniverseBalanceOf} - * Returns the omniverse balance of a user - */ - function omniverseBalanceOf(bytes calldata _pk) external view override returns (uint256) { - return omniverseBalances[_pk]; - } - - /** - * @notice See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual override returns (uint256) { - bytes storage pk = accountsMap[account]; - if (pk.length == 0) { - return 0; - } - else { - return omniverseBalances[pk]; - } - } - - /** - * @notice Receive and check an omniverse transaction - */ - function _omniverseTransaction(ERC6358TransactionData memory _data) internal { - // Check if the tx initiateSC is correct - bool found = false; - for (uint256 i = 0; i < members.length; i++) { - if (members[i].chainId == _data.chainId) { - require(keccak256(members[i].contractAddr) == keccak256(_data.initiateSC), "Wrong initiateSC"); - found = true; - } - } - require(found, "Wrong initiateSC"); - - // Check if the sender is honest - // to be continued, we can use block list instead of `isMalicious` - require(!isMalicious(_data.from), "User malicious"); - - // Verify the signature - VerifyResult verifyRet = OmniverseProtocolHelper.verifyTransaction(transactionRecorder[_data.from], _data); - - if (verifyRet == VerifyResult.Success) { - // Check cache - OmniverseTx storage cache = transactionCache[_data.from]; - require(cache.timestamp == 0, "Transaction cached"); - // Logic verification - _checkExecution(_data); - // Delays in executing - cache.txData = _data; - cache.timestamp = block.timestamp; - delayedTxs.push(DelayedTx(_data.from, _data.nonce)); - if (_data.chainId == chainId) { - emit TransactionSent(_data.from, _data.nonce); - } - } - else if (verifyRet == VerifyResult.Duplicated) { - emit TransactionExecuted(_data.from, _data.nonce); - } - else if (verifyRet == VerifyResult.Malicious) { - // Slash - } - } - - /** - * @notice Check if an omniverse transfer operation can be executed successfully - */ - function _checkOmniverseTransfer(bytes memory _from, uint256 _amount) internal view { - uint256 fromBalance = omniverseBalances[_from]; - require(fromBalance >= _amount, "Exceed balance"); - } - - /** - * @notice Exucute an omniverse transfer operation - */ - function _omniverseTransfer(bytes memory _from, bytes memory _to, uint256 _amount) internal { - _checkOmniverseTransfer(_from, _amount); - - uint256 fromBalance = omniverseBalances[_from]; - - unchecked { - omniverseBalances[_from] = fromBalance - _amount; - } - omniverseBalances[_to] += _amount; - - emit OmniverseTokenTransfer(_from, _to, _amount); - - address toAddr = _pkToAddress(_to); - accountsMap[toAddr] = _to; - } - - /** - * @notice Check if the public key is the owner - */ - function _checkOwner(bytes memory _pk) internal view { - address fromAddr = _pkToAddress(_pk); - require(fromAddr == owner(), "Not owner"); - } - - /** - * @notice Execute an omniverse mint operation - */ - function _omniverseMint(bytes memory _to, uint256 _amount) internal { - omniverseBalances[_to] += _amount; - emit OmniverseTokenTransfer("", _to, _amount); - - address toAddr = _pkToAddress(_to); - accountsMap[toAddr] = _to; - } - - /** - * @notice Check if an omniverse burn operation can be executed successfully - */ - function _checkOmniverseBurn(bytes memory _from, uint256 _amount) internal view { - uint256 fromBalance = omniverseBalances[_from]; - require(fromBalance >= _amount, "Exceed balance"); - } - - /** - * @notice Execute an omniverse burn operation - */ - function _omniverseBurn(bytes memory _from, uint256 _amount) internal { - omniverseBalances[_from] -= _amount; - emit OmniverseTokenTransfer(_from, "", _amount); - } - - /** - * @notice Convert the public key to evm address - */ - function _pkToAddress(bytes memory _pk) internal pure returns (address) { - bytes32 hash = keccak256(_pk); - return address(uint160(uint256(hash))); - } - - /** - * @notice Add new chain members to the token - */ - function setMembers(Member[] calldata _members) external onlyOwner { - for (uint256 i = 0; i < _members.length; i++) { - if (i < members.length) { - members[i] = _members[i]; - } - else { - members.push(_members[i]); - } - } - - for (uint256 i = _members.length; i < members.length; i++) { - delete members[i]; - } - } - - /** - * @notice Returns chain members of the token - */ - function getMembers() external view returns (Member[] memory) { - return members; - } - - /** - @notice See {IERC20-decimals}. - */ - function decimals() public view virtual override returns (uint8) { - return 12; - } - - /** - * @notice See IERC6358Fungible - */ - function getTransactionCount(bytes memory _pk) external override view returns (uint256) { - return transactionRecorder[_pk].txList.length; - } - - /** - * @notice See IERC6358Fungible - */ - function getTransactionData(bytes calldata _user, uint256 _nonce) external override view returns (ERC6358TransactionData memory txData, uint256 timestamp) { - RecordedCertificate storage rc = transactionRecorder[_user]; - OmniverseTx storage omniTx = rc.txList[_nonce]; - txData = omniTx.txData; - timestamp = omniTx.timestamp; - } - - /** - * @notice Set the cooling down time of an omniverse transaction - */ - function setCooingDownTime(uint256 _time) external { - cdTime = _time; - } - - /** - * @notice Index the user is malicious or not - */ - function isMalicious(bytes memory _pk) public view returns (bool) { - RecordedCertificate storage rc = transactionRecorder[_pk]; - return (rc.evilTxList.length > 0); - } - - /** - * @notice See IERC6358Fungible - */ - function getChainId() external view returns (uint32) { - return chainId; - } - - /** - * @notice Decode `_data` from bytes to Fungible - */ - function decodeData(bytes memory _data) internal pure returns (Fungible memory) { - (uint8 op, bytes memory exData, uint256 amount) = abi.decode(_data, (uint8, bytes, uint256)); - return Fungible(op, exData, amount); - } - - /** - * @notice See IERC6358Application - */ - function getPayloadRawData(bytes memory _payload) external pure returns (bytes memory) { - Fungible memory fungible = decodeData(_payload); - return abi.encodePacked(fungible.op, fungible.exData, uint128(fungible.amount)); - } -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol b/assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol deleted file mode 100644 index c8128b9..0000000 --- a/assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol +++ /dev/null @@ -1,411 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./libraries/OmniverseProtocolHelper.sol"; -import "./interfaces/IERC6358NonFungible.sol"; - -/** -* @notice Non-Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded -* -* @member op: The operation type -* NOTE op: 0-31 are reserved values, 32-255 are custom values -* op: 0 - omniverse account `from` transfers the token with id `tokenId` to omniverse account `exData`, `from` have the token with id `tokenId` -* op: 1 - omniverse account `from` mints the token with id `tokenId` to omniverse account `exData` -* op: 2 - omniverse account `from` burns the token with id `tokenId` from omniverse account `exData`, `exData` MUST have the token with id `tokenId` -* @member exData: The operation data. This sector could be empty and is determined by `op`. For example: - when `op` is 0 and 1, `exData` stores the omniverse account that receives. - when `op` is 2, `exData` is empty. -* @member tokenId: The token id of the non-fungible token being operated - */ -struct NonFungible { - uint8 op; - bytes exData; - uint256 tokenId; -} - -/** - * @notice Implementation of the {IERC6358NonFungible} interface - */ -contract ERC6358NonFungibleExample is Ownable, IERC6358NonFungible { - uint8 constant TRANSFER = 0; - uint8 constant MINT = 1; - uint8 constant BURN = 2; - - /** @notice Used to index a delayed transaction - * sender: The account which sent the transaction - * nonce: The nonce of the delayed transaction - */ - struct DelayedTx { - bytes sender; - uint256 nonce; - } - - /** - * @notice The member information - * chainId: The chain which the member belongs to - * contractAddr: The contract address on the member chain - */ - struct Member { - uint32 chainId; - bytes contractAddr; - } - - // Token name - string public name; - // Token symbol - string public symbol; - // Chain id used to distinguish different chains - uint32 chainId; - // O-transaction cooling down time - uint256 public cdTime; - // Omniverse accounts record - mapping(bytes => RecordedCertificate) transactionRecorder; - // Transactions to be executed - mapping(bytes => OmniverseTx) public transactionCache; - - // All information of chains on which the token is deployed - Member[] members; - // Token owners - mapping(uint256 => bytes) omniverseOwners; - // Omniverse balances - mapping(bytes => uint256) omniverseBalances; - // Delay-executing transactions - DelayedTx[] delayedTxs; - // Account map from evm address to public key - mapping(address => bytes) accountsMap; - - event OmniverseTokenTransfer(bytes from, bytes to, uint256 value); - - /** - * @notice Initiates the contract - * @param _chainId The chain which the contract is deployed on - * @param _name The name of the token - * @param _symbol The symbol of the token - */ - constructor(uint32 _chainId, string memory _name, string memory _symbol) { - chainId = _chainId; - name = _name; - symbol = _symbol; - } - - /** - * @notice See {IERC6358NonFungible-sendOmniverseTransaction} - * Send an omniverse transaction - */ - function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external override { - _omniverseTransaction(_data); - } - - /** - * @notice See {IERC6358NonFungible-triggerExecution} - */ - function triggerExecution() external { - require(delayedTxs.length > 0, "No delayed tx"); - - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - require(cache.timestamp != 0, "Not cached"); - require(cache.txData.nonce == delayedTxs[0].nonce, "Nonce error"); - (ERC6358TransactionData storage txData, uint256 timestamp) = (cache.txData, cache.timestamp); - require(block.timestamp >= timestamp + cdTime, "Not executable"); - delayedTxs[0] = delayedTxs[delayedTxs.length - 1]; - delayedTxs.pop(); - cache.timestamp = 0; - // Add to transaction recorder - RecordedCertificate storage rc = transactionRecorder[txData.from]; - rc.txList.push(cache); - - NonFungible memory nonFungible = decodeData(txData.payload); - if (nonFungible.op == TRANSFER) { - _omniverseTransfer(txData.from, nonFungible.exData, nonFungible.tokenId); - } - else if (nonFungible.op == MINT) { - _checkOwner(txData.from); - _omniverseMint(nonFungible.exData, nonFungible.tokenId); - } - else if (nonFungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(nonFungible.exData, nonFungible.tokenId); - _omniverseBurn(nonFungible.exData, nonFungible.tokenId); - } - } - - /** - * @notice Check if the transaction can be executed successfully - */ - function _checkExecution(ERC6358TransactionData memory txData) internal view { - NonFungible memory nonFungible = decodeData(txData.payload); - if (nonFungible.op == TRANSFER) { - _checkOmniverseTransfer(txData.from, nonFungible.tokenId); - } - else if (nonFungible.op == MINT) { - _checkOwner(txData.from); - _checkOmniverseMint(nonFungible.tokenId); - } - else if (nonFungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(nonFungible.exData, nonFungible.tokenId); - } - else { - revert("OP code error"); - } - } - - /** - * @notice Returns the nearest exexutable delayed transaction info - * or returns default if not found - */ - function getExecutableDelayedTx() external view returns (DelayedTx memory ret) { - if (delayedTxs.length > 0) { - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - if (block.timestamp >= cache.timestamp + cdTime) { - ret = delayedTxs[0]; - } - } - } - - /** - * @notice Returns the count of delayed transactions - */ - function getDelayedTxCount() external view returns (uint256) { - return delayedTxs.length; - } - - /** - * @notice See {IERC6358NonFungible-omniverseBalanceOf} - * Returns the omniverse balance of a user - */ - function omniverseBalanceOf(bytes calldata _pk) external view override returns (uint256) { - return omniverseBalances[_pk]; - } - - /** - * @notice See {IERC6358NonFungible-omniverseOwnerOf} - * Returns the owner of a token - */ - function omniverseOwnerOf(uint256 _tokenId) external view returns (bytes memory) { - bytes memory ret = omniverseOwners[_tokenId]; - require(keccak256(ret) != keccak256(bytes("")), "Token not exist"); - return ret; - } - - /** - * @notice See {IERC721-balanceOf}. - */ - function balanceOf(address account) public view returns (uint256) { - bytes storage pk = accountsMap[account]; - if (pk.length == 0) { - return 0; - } - else { - return omniverseBalances[pk]; - } - } - - /** - * @notice See {IERC721-ownerOf}. - */ - function ownerOf(uint256 tokenId) public view returns (address owner) { - bytes memory ret = this.omniverseOwnerOf(tokenId); - return _pkToAddress(ret); - } - - /** - * @notice Receive and check an omniverse transaction - */ - function _omniverseTransaction(ERC6358TransactionData memory _data) internal { - // Check if the tx initiateSC is correct - bool found = false; - for (uint256 i = 0; i < members.length; i++) { - if (members[i].chainId == _data.chainId) { - require(keccak256(members[i].contractAddr) == keccak256(_data.initiateSC), "Wrong initiateSC"); - found = true; - } - } - require(found, "Wrong initiateSC"); - - // Check if the sender is honest - // to be continued, we can use block list instead of `isMalicious` - require(!isMalicious(_data.from), "User malicious"); - - // Verify the signature - VerifyResult verifyRet = OmniverseProtocolHelper.verifyTransaction(transactionRecorder[_data.from], _data); - - if (verifyRet == VerifyResult.Success) { - // Check cache - OmniverseTx storage cache = transactionCache[_data.from]; - require(cache.timestamp == 0, "Transaction cached"); - // Logic verification - _checkExecution(_data); - // Delays in executing - cache.txData = _data; - cache.timestamp = block.timestamp; - delayedTxs.push(DelayedTx(_data.from, _data.nonce)); - if (_data.chainId == chainId) { - emit TransactionSent(_data.from, _data.nonce); - } - } - else if (verifyRet == VerifyResult.Duplicated) { - emit TransactionExecuted(_data.from, _data.nonce); - } - else if (verifyRet == VerifyResult.Malicious) { - // Slash - } - } - - /** - * @notice Check if an omniverse transfer operation can be executed successfully - */ - function _checkOmniverseTransfer(bytes memory _from, uint256 _tokenId) internal view { - require(keccak256(this.omniverseOwnerOf(_tokenId)) == keccak256(_from), "Not owner"); - } - - /** - * @notice Exucute an omniverse transfer operation - */ - function _omniverseTransfer(bytes memory _from, bytes memory _to, uint256 _tokenId) internal { - _checkOmniverseTransfer(_from, _tokenId); - - omniverseOwners[_tokenId] = _to; - omniverseBalances[_from] -= 1; - omniverseBalances[_to] += 1; - - emit OmniverseTokenTransfer(_from, _to, _tokenId); - - address toAddr = _pkToAddress(_to); - accountsMap[toAddr] = _to; - } - - /** - * @notice Check if the public key is the owner - */ - function _checkOwner(bytes memory _pk) internal view { - address fromAddr = _pkToAddress(_pk); - require(fromAddr == owner(), "Not owner"); - } - - /** - * @notice Check if an omniverse mint operation can be executed successfully - */ - function _checkOmniverseMint(uint256 _tokenId) internal view { - require(keccak256(omniverseOwners[_tokenId]) == keccak256(""), "Token already exist"); - } - - /** - * @notice Execute an omniverse mint operation - */ - function _omniverseMint(bytes memory _to, uint256 _tokenId) internal { - _checkOmniverseMint(_tokenId); - - omniverseOwners[_tokenId] = _to; - omniverseBalances[_to] += 1; - emit OmniverseTokenTransfer("", _to, _tokenId); - - address toAddr = _pkToAddress(_to); - accountsMap[toAddr] = _to; - } - - /** - * @notice Check if an omniverse burn operation can be executed successfully - */ - function _checkOmniverseBurn(bytes memory _from, uint256 _tokenId) internal view { - require(keccak256(this.omniverseOwnerOf(_tokenId)) == keccak256(_from), "Not token owner"); - } - - /** - * @notice Execute an omniverse burn operation - */ - function _omniverseBurn(bytes memory _from, uint256 _tokenId) internal { - delete omniverseOwners[_tokenId]; - omniverseBalances[_from] -= 1; - emit OmniverseTokenTransfer(_from, "", _tokenId); - } - - /** - * @notice Convert the public key to evm address - */ - function _pkToAddress(bytes memory _pk) internal pure returns (address) { - bytes32 hash = keccak256(_pk); - return address(uint160(uint256(hash))); - } - - /** - * @notice Add new chain members to the token - */ - function setMembers(Member[] calldata _members) external onlyOwner { - for (uint256 i = 0; i < _members.length; i++) { - if (i < members.length) { - members[i] = _members[i]; - } - else { - members.push(_members[i]); - } - } - - for (uint256 i = _members.length; i < members.length; i++) { - delete members[i]; - } - } - - /** - * @notice Returns chain members of the token - */ - function getMembers() external view returns (Member[] memory) { - return members; - } - - /** - * @notice See IERC6358NonFungible - */ - function getTransactionCount(bytes memory _pk) external override view returns (uint256) { - return transactionRecorder[_pk].txList.length; - } - - /** - * @notice See IERC6358NonFungible - */ - function getTransactionData(bytes calldata _user, uint256 _nonce) external override view returns (ERC6358TransactionData memory txData, uint256 timestamp) { - RecordedCertificate storage rc = transactionRecorder[_user]; - OmniverseTx storage omniTx = rc.txList[_nonce]; - txData = omniTx.txData; - timestamp = omniTx.timestamp; - } - - /** - * @notice Set the cooling down time of an omniverse transaction - */ - function setCooingDownTime(uint256 _time) external { - cdTime = _time; - } - - /** - * @notice Index the user is malicious or not - */ - function isMalicious(bytes memory _pk) public view returns (bool) { - RecordedCertificate storage rc = transactionRecorder[_pk]; - return (rc.evilTxList.length > 0); - } - - /** - * @notice See IERC6358NonFungible - */ - function getChainId() external view returns (uint32) { - return chainId; - } - - /** - * @notice Decode `_data` from bytes to Fungible - */ - function decodeData(bytes memory _data) internal pure returns (NonFungible memory) { - (uint8 op, bytes memory exData, uint256 tokenId) = abi.decode(_data, (uint8, bytes, uint256)); - return NonFungible(op, exData, tokenId); - } - - /** - * @notice See IERC6358Application - */ - function getPayloadRawData(bytes memory _payload) external pure returns (bytes memory) { - NonFungible memory nonFungible = decodeData(_payload); - return abi.encodePacked(nonFungible.op, nonFungible.exData, uint128(nonFungible.tokenId)); - } -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358.sol deleted file mode 100644 index 834c90a..0000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -/** - * @notice Omniverse transaction data structure - * @member nonce: The number of the o-transactions. If the current nonce of an omniverse account is `k`, the valid nonce of this o-account in the next o-transaction is `k+1`. - * @member chainId: The chain where the o-transaction is initiated - * @member initiateSC: The contract address from which the o-transaction is first initiated - * @member from: The Omniverse account which signs the o-transaction - * @member payload: The encoded bussiness logic data, which is maintained by the developer - * @member signature: The signature of the above informations. - */ -struct ERC6358TransactionData { - uint128 nonce; - uint32 chainId; - bytes initiateSC; - bytes from; - bytes payload; - bytes signature; -} - -/** - * @notice Interface of the ERC Omniverse-DLT - */ -interface IERC6358 { - /** - * @notice Emitted when a o-transaction which has nonce `nonce` and was signed by user `pk` is sent by calling {sendOmniverseTransaction} - */ - event TransactionSent(bytes pk, uint256 nonce); - - /** - * @notice Sends an omniverse transaction - * @dev - * Note: MUST implement the validation of the `_data.signature` - * Note: A map maintaining the omniverse account and the related transaction nonce is RECOMMENDED - * Note: MUST implement the validation of the `_data.nonce` according to the current account nonce - * Note: MUST implement the validation of the `_data. payload` - * Note: This interface is just for sending an omniverse transaction, and the execution MUST NOT be within this interface - * Note: The actual execution of an omniverse transaction is RECOMMENDED to be in another function and MAY be delayed for a time, - * which is determined all by who publishes an O-DLT token - * @param _data: the omniverse transaction data with type {ERC6358TransactionData} - * See more information in the defination of {ERC6358TransactionData} - * - * Emit a {TransactionSent} event - */ - function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external; - - /** - * @notice Get the number of omniverse transactions sent by user `_pk`, - * which is also the valid `nonce` of a new omniverse transactions of user `_pk` - * @param _pk: Omniverse account to be queried - * @return The number of omniverse transactions sent by user `_pk` - */ - function getTransactionCount(bytes memory _pk) external view returns (uint256); - - /** - * @notice Get the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce` - * @param _user Omniverse account to be queried - * @param _nonce The nonce to be queried - * @return Returns the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce` - */ - function getTransactionData(bytes calldata _user, uint256 _nonce) external view returns (ERC6358TransactionData memory, uint256); - - /** - * @notice Get the chain ID - * @return Returns the chain ID - */ - function getChainId() external view returns (uint32); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358Application.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358Application.sol deleted file mode 100644 index fcf168a..0000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358Application.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -/** - * @notice Interface of the omniverse application contract - */ -interface IERC6358Application { - /** - * @notice Emitted when a o-transaction which has nonce `nonce` and was signed by user `pk` is executed - */ - event TransactionExecuted(bytes pk, uint256 nonce); - - /** - * @notice From the `_payload`, calculate the raw data which is used to generate signature - * @param _payload Original data committed by synchronizers, and stored in hostorical transaction list - * @return Returns The raw data of `_payload` - */ - function getPayloadRawData(bytes memory _payload) external pure returns (bytes memory); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol deleted file mode 100644 index a82c923..0000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "./IERC6358.sol"; -import "./IERC6358Application.sol"; - -/** - * @notice Interface of the omniverse fungible token, which inherits {IERC6358} - */ -interface IERC6358Fungible is IERC6358, IERC6358Application { - /** - * @notice Get the omniverse balance of a user `_pk` - * @param _pk Omniverse account to be queried - * @return Returns the omniverse balance of a user `_pk` - */ - function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol deleted file mode 100644 index 5b62ed6..0000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "./IERC6358.sol"; -import "./IERC6358Application.sol"; - -/** - * @notice Interface of the omniverse non fungible token, which inherits {IERC6358} - */ -interface IERC6358NonFungible is IERC6358, IERC6358Application { - /** - * @notice Get the number of tokens in account `_pk` - * @param _pk Omniverse account to be queried - * @return Returns the number of tokens in account `_pk` - */ - function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256); - - /** - * @notice Get the owner of a token `tokenId` - * @param _tokenId Omniverse token id to be queried - * @return Returns the owner of a token `tokenId` - */ - function omniverseOwnerOf(uint256 _tokenId) external view returns (bytes memory); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol b/assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol deleted file mode 100644 index f7144fa..0000000 --- a/assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "../interfaces/IERC6358.sol"; -import "../interfaces/IERC6358Application.sol"; - -/** - * @notice Used to record one omniverse transaction data - * txData: The original omniverse transaction data committed to the contract - * timestamp: When the omniverse transaction data is committed - */ -struct OmniverseTx { - ERC6358TransactionData txData; - uint256 timestamp; -} - -/** - * @notice An malicious omniverse transaction data - * oData: The recorded omniverse transaction data - * hisNonce: The nonce of the historical transaction which it conflicts with - */ -struct EvilTxData { - OmniverseTx oData; - uint256 hisNonce; -} - -/** - * @notice Used to record the historical omniverse transactions of a user - * txList: Successful historical omniverse transaction list - * evilTxList: Malicious historical omniverse transaction list - */ -struct RecordedCertificate { - OmniverseTx[] txList; - EvilTxData[] evilTxList; -} - -// Result of verification of an omniverse transaction -enum VerifyResult { - Success, - Malicious, - Duplicated -} - -/** - * @notice The library is mainly responsible for omniverse transaction verification and - * provides some basic methods. - * NOTE The verification method is for reference only, and developers can design appropriate - * verification mechanism based on their bussiness logic. - */ -library OmniverseProtocolHelper { - /** - * @notice Get the hash of a transaction - */ - function getTransactionHash(ERC6358TransactionData memory _data) internal view returns (bytes32) { - bytes memory payloadRawData = IERC6358Application(address(this)).getPayloadRawData(_data.payload); - bytes memory rawData = abi.encodePacked(_data.nonce, _data.chainId, _data.initiateSC, _data.from, payloadRawData); - return keccak256(rawData); - } - - /** - * @notice Recover the address - */ - function recoverAddress(bytes32 _hash, bytes memory _signature) public pure returns (address) { - uint8 v; - bytes32 r; - bytes32 s; - assembly { - r := mload(add(_signature, 32)) - s := mload(add(_signature, 64)) - v := mload(add(_signature, 65)) - } - address recovered = ecrecover(_hash, v, r, s); - require(recovered != address(0), "Verify failed"); - return recovered; - } - - /** - * @notice Check if the public key matches the recovered address - */ - function checkPkMatched(bytes memory _pk, address _address) public pure { - bytes32 hash = keccak256(_pk); - address pkAddress = address(uint160(uint256(hash))); - require(_address == pkAddress, "Signer not sender"); - } - - /** - * @notice Verify an omniverse transaction - */ - function verifyTransaction(RecordedCertificate storage rc, ERC6358TransactionData memory _data) public returns (VerifyResult) { - uint256 nonce = rc.txList.length; - - bytes32 txHash = getTransactionHash(_data); - address recoveredAddress = recoverAddress(txHash, _data.signature); - // Signature verified failed - checkPkMatched(_data.from, recoveredAddress); - - // Check nonce - if (nonce == _data.nonce) { - return VerifyResult.Success; - } - else if (nonce > _data.nonce) { - // The message has been received, check conflicts - OmniverseTx storage hisTx = rc.txList[_data.nonce]; - bytes32 hisTxHash = getTransactionHash(hisTx.txData); - if (hisTxHash != txHash) { - // to be continued, add to evil list, but can not be duplicated - EvilTxData storage evilTx = rc.evilTxList.push(); - evilTx.hisNonce = nonce; - evilTx.oData.txData = _data; - evilTx.oData.timestamp = block.timestamp; - return VerifyResult.Malicious; - } - else { - return VerifyResult.Duplicated; - } - } - else { - revert("Nonce error"); - } - } -} \ No newline at end of file diff --git a/assets/eip-6358/src/migrations/2_deploy_contracts.js b/assets/eip-6358/src/migrations/2_deploy_contracts.js deleted file mode 100644 index 353b16c..0000000 --- a/assets/eip-6358/src/migrations/2_deploy_contracts.js +++ /dev/null @@ -1,38 +0,0 @@ -const OmniverseProtocolHelper = artifacts.require("OmniverseProtocolHelper"); -const ERC6358FungibleExample = artifacts.require("ERC6358FungibleExample"); -const ERC6358NonFungibleExample = artifacts.require("ERC6358NonFungibleExample"); -// const fs = require("fs"); - -const CHAIN_IDS = { - GOERLI: 1, - BSCTEST: 2, - MOCK: 10000, -}; - -module.exports = async function (deployer, network) { - // const contractAddressFile = './config/default.json'; - // let data = fs.readFileSync(contractAddressFile, 'utf8'); - // let jsonData = JSON.parse(data); - if (network == 'development') { - return; - } - // else if(!jsonData[network]) { - // console.error('There is no config for: ', network, ', please add.'); - // return; - // } - - await deployer.deploy(OmniverseProtocolHelper); - await deployer.link(OmniverseProtocolHelper, ERC6358FungibleExample); - await deployer.link(OmniverseProtocolHelper, ERC6358NonFungibleExample); - await deployer.deploy(ERC6358FungibleExample, CHAIN_IDS[network], "X", "X"); - await deployer.deploy(ERC6358NonFungibleExample, CHAIN_IDS[network], "X", "X"); - - // Update config - if (network.indexOf('-fork') != -1 || network == 'test' || network == 'development') { - return; - } - - // jsonData[network].ERC6358FungibleExampleAddress = ERC6358FungibleExample.address; - // jsonData[network].ERC6358NonFungibleExampleAddress = ERC6358NonFungibleExample.address; - // fs.writeFileSync(contractAddressFile, JSON.stringify(jsonData, null, '\t')); -}; diff --git a/assets/eip-6358/src/package.json b/assets/eip-6358/src/package.json deleted file mode 100644 index 63c4f50..0000000 --- a/assets/eip-6358/src/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "contracts", - "version": "1.0.0", - "description": "", - "main": "truffle-config.js", - "directories": { - "test": "test" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.7.3", - "@truffle/hdwallet-provider": "^2.1.0", - "bn.js": "^5.2.1", - "commander": "^9.4.1", - "config": "^3.3.8", - "eccrypto": "^1.1.6", - "keccak256": "^1.0.6", - "secp256k1": "^4.0.3", - "web3": "^1.8.0" - }, - "devDependencies": {}, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC" -} diff --git a/assets/eip-6358/src/test/.gitkeep b/assets/eip-6358/src/test/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/assets/eip-6358/src/test/ERC6358Fungible.test.js b/assets/eip-6358/src/test/ERC6358Fungible.test.js deleted file mode 100644 index bc0cbb6..0000000 --- a/assets/eip-6358/src/test/ERC6358Fungible.test.js +++ /dev/null @@ -1,404 +0,0 @@ -const utils = require('./utils'); -const BN = require('bn.js'); -const secp256k1 = require('secp256k1'); -const keccak256 = require('keccak256'); -const Web3 = require('web3'); -const web3js = new Web3(Web3.givenProvider); -const assert = require('assert'); -// const { util } = require('config'); - -const CHAIN_ID = 0; -const ONE_TOKEN = '1000000000000000000'; -const TEN_TOKEN = '10000000000000000000'; -const TOKEN_SYMBOL = 'ERC6358Fungible'; -const COOL_DOWN = 2; - -const TRANSFER = 0; -const MINT = 1; -const BURN = 2; - -const Fungible = artifacts.require('./ERC6358FungibleExample.sol'); -const OmniverseProtocolHelper = artifacts.require('./OmniverseProtocolHelper.sol'); -Fungible.defaults({ - gas: 8000000, -}); - -Fungible.numberFormat = 'String'; - -const owner = '0xe092b1fa25DF5786D151246E492Eed3d15EA4dAA'; -const user1 = '0xc0d8F541Ab8B71F20c10261818F2F401e8194049'; -const user2 = '0xf1F8Ef6b4D4Ba31079E2263eC85c03fD5a0802bF'; - -const ownerPk = '0xb0c4ae6f28a5579cbeddbf40b2209a5296baf7a4dc818f909e801729ecb5e663dce22598685e985a6ed1a557cf2145deba5290418b3cc00680a90accc9b93522'; -const user1Pk = '0x99f5789b8b0d903a6e868c5fb9971eedde37da046e69d49c903a1b33167e0f76d1f1269628bfcff54e0581a0b019502394754e900dcbb69bf30010d51967d780'; -const user2Pk = '0x25607735c05d91b504425c25567154aea2fd07e9a515b7872c7f783aa58333942b9d6ac3afacdccfe2585d1a4617f23a802a32bb6abafe13aaba2d386d44f52d'; - -const ownerSk = Buffer.from('0cc0c2de7e8c30525b4ca3b9e0b9703fb29569060d403261055481df7014f7fa', 'hex'); -const user1Sk = Buffer.from('b97de1848f97378ee439b37e776ffe11a2fff415b2f93dc240b2d16e9c184ba9', 'hex'); -const user2Sk = Buffer.from('42f3b9b31fcaaa03ca71cab7d194979d0d1bedf16f8f4e9414f0ed4df699dd10', 'hex'); - -let signData = (hash, sk) => { - let signature = secp256k1.ecdsaSign(Uint8Array.from(hash), Uint8Array.from(sk)); - return '0x' + Buffer.from(signature.signature).toString('hex') + (signature.recid == 0 ? '1b' : '1c'); -} - -let getRawData = (txData, op, params) => { - let bData; - if (op == MINT) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == TRANSFER) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == BURN) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - let ret = Buffer.concat([Buffer.from(new BN(txData.nonce).toString('hex').padStart(32, '0'), 'hex'), Buffer.from(new BN(txData.chainId).toString('hex').padStart(8, '0'), 'hex'), - Buffer.from(txData.initiateSC.slice(2), 'hex'), Buffer.from(txData.from.slice(2), 'hex'), bData]); - return ret; -} - -let encodeMint = (from, toPk, amount, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: Fungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [MINT, toPk, amount]) - } - let bData = getRawData(txData, MINT, [toPk, amount]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeBurn = (from, toPk, amount, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: Fungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [BURN, toPk, amount]) - } - let bData = getRawData(txData, BURN, [toPk, amount]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeTransfer = (from, toPk, amount, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: Fungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [TRANSFER, toPk, amount]) - } - let bData = getRawData(txData, TRANSFER, [toPk, amount]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -contract('ERC6358Fungible', function() { - before(async function() { - await initContract(); - }); - - let fungible; - - let initContract = async function() { - let protocol = await OmniverseProtocolHelper.new(); - Fungible.link(protocol); - fungible = await Fungible.new(CHAIN_ID, TOKEN_SYMBOL, TOKEN_SYMBOL, {from: owner}); - Fungible.address = fungible.address; - await fungible.setMembers([[CHAIN_ID, Fungible.address]]); - await fungible.setCooingDownTime(COOL_DOWN); - } - - const mintToken = async function(from, toPk, amount) { - let nonce = await fungible.getTransactionCount(from.pk); - let txData = encodeMint(from, toPk, amount, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await fungible.triggerExecution(); - } - - describe('Verify transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Signature error', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - txData.signature = txData.signature.slice(0, -2); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Verify failed'); - }); - }); - - describe('Sender not signer', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - txData.from = ownerPk; - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Signer not sender'); - }); - }); - - describe('Nonce error', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk) + 20; - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Nonce error'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - let ret = await fungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionSent'); - let count = await fungible.getTransactionCount(ownerPk); - assert(count == 0, "The count should be zero"); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - ret = await fungible.triggerExecution(); - count = await fungible.getTransactionCount(ownerPk); - assert(count == 1, "The count should be one"); - }); - }); - - describe('Cooling down', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Transaction cached'); - }); - }); - - describe('Transaction duplicated', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - let ret = await fungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionExecuted'); - }); - }); - - describe('Cooled down', function() { - it('should succeed', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let nonce = await fungible.getTransactionCount(ownerPk); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - ret = await fungible.triggerExecution(); - let count = await fungible.getTransactionCount(ownerPk); - assert(count == 2); - }); - }); - - describe('Malicious', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - let malicious = await fungible.isMalicious(ownerPk); - assert(malicious, "It should be malicious"); - }); - }); - }); - - describe('Omniverse Transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Wrong initiate smart contract', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - txData.initiateSC = user1; - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Wrong initiateSC'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - let count = await fungible.getDelayedTxCount(); - assert(count == 1, 'The number of delayed txs should be one'); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - ret = await fungible.triggerExecution(); - }); - }); - - describe('Malicious transaction', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - let count = await fungible.getDelayedTxCount(); - assert(count == 0, 'The number of delayed txs should be zero'); - }); - }); - - describe('User is malicious', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'User malicious'); - }); - }); - }); - - describe('Get executable delayed transaction', function() { - before(async function() { - await initContract(); - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - }); - - describe('Cooling down', function() { - it('should be none', async () => { - let tx = await fungible.getExecutableDelayedTx(); - assert(tx.sender == '0x', 'There should be no transaction'); - }); - }); - - describe('Cooled down', function() { - it('should be one transaction', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let tx = await fungible.getExecutableDelayedTx(); - assert(tx.sender == ownerPk, 'There should be one transaction'); - }); - }); - }); - - describe('Trigger execution', function() { - before(async function() { - await initContract(); - }); - - describe('No delayed transaction', function() { - it('should fail', async () => { - await utils.expectThrow(fungible.triggerExecution(), 'No delayed tx'); - }); - }); - - describe('Not executable', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.expectThrow(fungible.triggerExecution(), 'Not executable'); - }); - }); - }); - - describe('Mint', function() { - before(async function() { - await initContract(); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeMint({pk: user2Pk, sk: user2Sk}, user1Pk, ONE_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Is owner', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await fungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await fungible.omniverseBalanceOf(user1Pk); - assert(ONE_TOKEN == balance, 'Balance should be one'); - }); - }); - }); - - describe('Burn', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeBurn({pk: user2Pk, sk: user2Sk}, user1Pk, ONE_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Exceed balance', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), "Exceed balance"); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await fungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await fungible.omniverseBalanceOf(user1Pk); - assert(0 == balance, 'Balance should be zero'); - }); - }); - }); - - describe('Transfer', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN); - }); - - describe('Exceed balance', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Exceed balance'); - }); - }); - - describe('Balance enough', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, ONE_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await fungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await fungible.omniverseBalanceOf(user1Pk); - assert('0' == balance, 'Balance should be zero'); - balance = await fungible.omniverseBalanceOf(user2Pk); - assert(ONE_TOKEN == balance, 'Balance should be one'); - }); - }); - }); -}); \ No newline at end of file diff --git a/assets/eip-6358/src/test/ERC6358NonFungible.test.js b/assets/eip-6358/src/test/ERC6358NonFungible.test.js deleted file mode 100644 index ffce374..0000000 --- a/assets/eip-6358/src/test/ERC6358NonFungible.test.js +++ /dev/null @@ -1,408 +0,0 @@ -const utils = require('./utils'); -const BN = require('bn.js'); -const secp256k1 = require('secp256k1'); -const keccak256 = require('keccak256'); -const Web3 = require('web3'); -const web3js = new Web3(Web3.givenProvider); -const assert = require('assert'); -// const { util } = require('config'); - -const CHAIN_ID = 0; -const TOKEN_ID = 1; -const TOKEN_SYMBOL = 'ERC6358NonFungible'; -const COOL_DOWN = 2; - -const TRANSFER = 0; -const MINT = 1; -const BURN = 2; - -const NonFungible = artifacts.require('./ERC6358NonFungibleExample.sol'); -const OmniverseProtocolHelper = artifacts.require('./OmniverseProtocolHelper.sol'); -NonFungible.defaults({ - gas: 8000000, -}); - -NonFungible.numberFormat = 'String'; - -const owner = '0xe092b1fa25DF5786D151246E492Eed3d15EA4dAA'; -const user1 = '0xc0d8F541Ab8B71F20c10261818F2F401e8194049'; -const user2 = '0xf1F8Ef6b4D4Ba31079E2263eC85c03fD5a0802bF'; - -const ownerPk = '0xb0c4ae6f28a5579cbeddbf40b2209a5296baf7a4dc818f909e801729ecb5e663dce22598685e985a6ed1a557cf2145deba5290418b3cc00680a90accc9b93522'; -const user1Pk = '0x99f5789b8b0d903a6e868c5fb9971eedde37da046e69d49c903a1b33167e0f76d1f1269628bfcff54e0581a0b019502394754e900dcbb69bf30010d51967d780'; -const user2Pk = '0x25607735c05d91b504425c25567154aea2fd07e9a515b7872c7f783aa58333942b9d6ac3afacdccfe2585d1a4617f23a802a32bb6abafe13aaba2d386d44f52d'; - -const ownerSk = Buffer.from('0cc0c2de7e8c30525b4ca3b9e0b9703fb29569060d403261055481df7014f7fa', 'hex'); -const user1Sk = Buffer.from('b97de1848f97378ee439b37e776ffe11a2fff415b2f93dc240b2d16e9c184ba9', 'hex'); -const user2Sk = Buffer.from('42f3b9b31fcaaa03ca71cab7d194979d0d1bedf16f8f4e9414f0ed4df699dd10', 'hex'); - -let signData = (hash, sk) => { - let signature = secp256k1.ecdsaSign(Uint8Array.from(hash), Uint8Array.from(sk)); - return '0x' + Buffer.from(signature.signature).toString('hex') + (signature.recid == 0 ? '1b' : '1c'); -} - -let getRawData = (txData, op, params) => { - let bData; - if (op == MINT) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == TRANSFER) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == BURN) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - let ret = Buffer.concat([Buffer.from(new BN(txData.nonce).toString('hex').padStart(32, '0'), 'hex'), Buffer.from(new BN(txData.chainId).toString('hex').padStart(8, '0'), 'hex'), - Buffer.from(txData.initiateSC.slice(2), 'hex'), Buffer.from(txData.from.slice(2), 'hex'), bData]); - return ret; -} - -let encodeMint = (from, toPk, tokenId, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: NonFungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [MINT, toPk, tokenId]) - } - let bData = getRawData(txData, MINT, [toPk, tokenId]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeBurn = (from, toPk, tokenId, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: NonFungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [BURN, toPk, tokenId]) - } - let bData = getRawData(txData, BURN, [toPk, tokenId]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeTransfer = (from, toPk, tokenId, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: NonFungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [TRANSFER, toPk, tokenId]) - } - let bData = getRawData(txData, TRANSFER, [toPk, tokenId]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -contract('ERC6358NonFungible', function() { - before(async function() { - await initContract(); - }); - - let nonFungible; - - let initContract = async function() { - let protocol = await OmniverseProtocolHelper.new(); - NonFungible.link(protocol); - nonFungible = await NonFungible.new(CHAIN_ID, TOKEN_SYMBOL, TOKEN_SYMBOL, {from: owner}); - NonFungible.address = nonFungible.address; - await nonFungible.setMembers([[CHAIN_ID, NonFungible.address]]); - await nonFungible.setCooingDownTime(COOL_DOWN); - } - - const mintToken = async function(from, toPk, tokenId) { - let nonce = await nonFungible.getTransactionCount(from.pk); - let txData = encodeMint(from, toPk, tokenId, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await nonFungible.triggerExecution(); - } - - describe('Verify transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Signature error', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - txData.signature = txData.signature.slice(0, -2); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Verify failed'); - }); - }); - - describe('Sender not signer', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - txData.from = ownerPk; - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Signer not sender'); - }); - }); - - describe('Nonce error', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk) + 20; - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Nonce error'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - let ret = await nonFungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionSent'); - let count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 0, "The count should be zero"); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - ret = await nonFungible.triggerExecution(); - count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 1, "The count should be one"); - }); - }); - - describe('Cooling down', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID + 1, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Transaction cached'); - }); - }); - - describe('Transaction duplicated', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - let ret = await nonFungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionExecuted'); - }); - }); - - describe('Cooled down', function() { - it('should succeed', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let nonce = await nonFungible.getTransactionCount(ownerPk); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - ret = await nonFungible.triggerExecution(); - let count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 2); - }); - }); - - describe('Malicious', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID + 1, nonce); - await nonFungible.sendOmniverseTransaction(txData); - let malicious = await nonFungible.isMalicious(ownerPk); - assert(malicious, "It should be malicious"); - }); - }); - }); - - describe('Omniverse Transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Wrong initiate smart contract', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - txData.initiateSC = user1; - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Wrong initiateSC'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - let count = await nonFungible.getDelayedTxCount(); - assert(count == 1, 'The number of delayed txs should be one'); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - ret = await nonFungible.triggerExecution(); - }); - }); - - describe('Malicious transaction', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - let count = await nonFungible.getDelayedTxCount(); - assert(count == 0, 'The number of delayed txs should be zero'); - }); - }); - - describe('User is malicious', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'User malicious'); - }); - }); - }); - - describe('Get executable delayed transaction', function() { - before(async function() { - await initContract(); - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - }); - - describe('Cooling down', function() { - it('should be none', async () => { - let tx = await nonFungible.getExecutableDelayedTx(); - assert(tx.sender == '0x', 'There should be no transaction'); - }); - }); - - describe('Cooled down', function() { - it('should be one transaction', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let tx = await nonFungible.getExecutableDelayedTx(); - assert(tx.sender == ownerPk, 'There should be one transaction'); - }); - }); - }); - - describe('Trigger execution', function() { - before(async function() { - await initContract(); - }); - - describe('No delayed transaction', function() { - it('should fail', async () => { - await utils.expectThrow(nonFungible.triggerExecution(), 'No delayed tx'); - }); - }); - - describe('Not executable', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.expectThrow(nonFungible.triggerExecution(), 'Not executable'); - }); - }); - }); - - describe('Mint', function() { - before(async function() { - await initContract(); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeMint({pk: user2Pk, sk: user2Sk}, user1Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Is owner', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await nonFungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let tokenOwner = await nonFungible.omniverseOwnerOf(TOKEN_ID); - assert(user1Pk == tokenOwner, 'Owner should be user1'); - let balance = await nonFungible.omniverseBalanceOf(user1Pk); - assert(TOKEN_ID == balance, 'Balance should be one'); - }); - }); - }); - - describe('Burn', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeBurn({pk: user2Pk, sk: user2Sk}, user1Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Token not exist', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID + 1, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), "Token not exist"); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await nonFungible.triggerExecution(); - await utils.expectThrow(nonFungible.omniverseOwnerOf(TOKEN_ID), "Token not exist"); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await nonFungible.omniverseBalanceOf(user1Pk); - assert(0 == balance, 'Balance should be zero'); - }); - }); - }); - - describe('Transfer', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID); - }); - - describe('Not token owner', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user2Pk, sk: user2Sk}, user2Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Not owner'); - }); - }); - - describe('Balance enough', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1); - let ret = await nonFungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let tokenOwner = await nonFungible.omniverseOwnerOf(TOKEN_ID); - assert(user2Pk == tokenOwner, 'Token owner should be user2'); - let balance = await nonFungible.omniverseBalanceOf(user1Pk); - assert('0' == balance, 'Balance should be zero'); - balance = await nonFungible.omniverseBalanceOf(user2Pk); - assert(TOKEN_ID == balance, 'Balance should be one'); - }); - }); - }); -}); \ No newline at end of file diff --git a/assets/eip-6358/src/test/utils.js b/assets/eip-6358/src/test/utils.js deleted file mode 100644 index 260ac7a..0000000 --- a/assets/eip-6358/src/test/utils.js +++ /dev/null @@ -1,85 +0,0 @@ -const Web3 = require('web3'); - -const expectThrow = async (promise, message) => { - try { - await promise; - } - catch (err) { - if (!message) { - const outOfGas = err.message.includes("out of gas"); - const invalidOpcode = err.message.includes("invalid opcode"); - assert( - outOfGas || invalidOpcode, - "Expected throw, got `" + err + "` instead" - ); - } - else { - const expectedException = err.message.includes(message); - assert(expectedException, - "Expected throw, got `" + err + "` instead") - } - return; - } - assert.fail("Expected throw not received"); -}; - -// Convert normal string to u8 array -function stringToByteArray(str) { - return Array.from(str, function(byte) { - return byte.charCodeAt(0); - }); -} - -// Convert u8 array to hex string -function toHexString(byteArray) { - return '0x' + Array.from(byteArray, function(byte) { - return ('0' + (byte & 0xFF).toString(16)).slice(-2); - }).join('') -} - -// Mine one block -async function evmMineOneBlock (web3js) { - await new Promise((resolve, reject) => { - web3js.send({ - jsonrpc: "2.0", - method: "evm_mine", - id: new Date().getTime() - }, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); -}; - -async function sleep(seconds) { - await new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, seconds * 1000); - }); -} - -// Mine blocks -async function evmMine (num) { - for (let i = 0; i < num; i++) { - await evmMineOneBlock(Web3.givenProvider); - } -}; - -// Returns the latest block -async function getBlock() { - const web3js = new Web3(Web3.givenProvider); - let block = await web3js.eth.getBlock("latest"); - return block; -} - -module.exports = { - stringToByteArray: stringToByteArray, - toHexString: toHexString, - expectThrow: expectThrow, - evmMine: evmMine, - getBlock: getBlock, - sleep: sleep -} \ No newline at end of file diff --git a/assets/eip-6366/contracts/EIP6366Core.sol b/assets/eip-6366/contracts/EIP6366Core.sol deleted file mode 100644 index 91e54d6..0000000 --- a/assets/eip-6366/contracts/EIP6366Core.sol +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; -import "./interfaces/IEIP6366Error.sol"; -import "./interfaces/IEIP6366Core.sol"; - -/** - * @dev Implement the core of EIP-6366 - */ -contract EIP6366Core is IEIP6366Core { - /** - * @dev Stored permission of an address - */ - mapping(address => uint256) private permissions; - - /** - * @dev Stored delegation information - */ - mapping(bytes32 => uint256) private delegations; - - /** - * @dev Transfer a subset of owning permission to a target address - * @param _to New permission owner's address - * @param _permission A subset of owning permission - */ - function transfer( - address _to, - uint256 _permission - ) external virtual override returns (bool success) { - return _transfer(_to, _permission); - } - - /** - * @dev Allowed a delegatee to act for permission owner's behalf - * @param _delegatee Delegatee address - * @param _permission A subset of permission - */ - function approve( - address _delegatee, - uint256 _permission - ) external virtual override returns (bool success) { - return _approve(_delegatee, _permission); - } - - /** - * @dev Get all owning permission of an address - * @param _owner Permission owner's address - */ - function permissionOf( - address _owner - ) external view virtual override returns (uint256 permission) { - return _permissionOf(_owner); - } - - /** - * @dev Checking the existance of required permission on a given permission set - * @param _required Required permission set - * @param _permission Checking permission set - */ - function permissionRequire( - uint256 _permission, - uint256 _required - ) external view virtual override returns (bool isPermissioned) { - return _permissionRequire(_permission, _required); - } - - /** - * @dev Checking if an actor has sufficient permission, by himself or from a delegation, on a given permission set - * @param _owner Permission owner's address - * @param _actor Actor's address - * @param _required Required permission set - */ - function hasPermission( - address _owner, - address _actor, - uint256 _required - ) external view override returns (bool isPermissioned) { - return _hasPermission(_owner, _actor, _required); - } - - /** - * @dev Get delegated permission that owner approved to delegatee - * @param _owner Permission owner's address - * @param _delegatee Delegatee's address - */ - function delegated( - address _owner, - address _delegatee - ) external view virtual override returns (uint256 permission) { - return _delegated(_owner, _delegatee); - } - - /** - * @dev Mint a new set of permission to a new owner - * @param _owner New permission owner - * @param _permission Permission - */ - function _mint( - address _owner, - uint256 _permission - ) internal returns (bool) { - permissions[_owner] = _permission; - emit Transfer(address(0x0), _owner, _permission); - return true; - } - - /** - * @dev Burn all permission of the owner - * @param _owner New permission owner - */ - function _burn(address _owner) internal returns (bool) { - emit Transfer(_owner, address(0x0), permissions[_owner]); - permissions[_owner] = 0; - return true; - } - - /** - * @dev Create an unique key that linked permission owner and delegatee - * @param _owner Permission owner's address - * @param _delegatee Delegate's address - */ - function _uniqueKey( - address _owner, - address _delegatee - ) private pure returns (bytes32) { - return keccak256(abi.encodePacked(_owner, _delegatee)); - } - - function _transfer( - address _to, - uint256 _permission - ) internal returns (bool success) { - address owner = msg.sender; - // Prevent permission to be burnt - if (permissions[_to] & _permission > 0) { - revert IEIP6366Error.DuplicatedPermission(_permission); - } - // Clean subset of permission from owner - permissions[owner] = permissions[owner] ^ _permission; - // Set subset of permission to new owner - permissions[_to] = permissions[_to] | _permission; - emit Transfer(owner, _to, _permission); - return true; - } - - function _approve( - address _delegatee, - uint256 _permission - ) internal returns (bool success) { - address owner = msg.sender; - delegations[_uniqueKey(owner, _delegatee)] = _permission; - emit Approval(owner, _delegatee, _permission); - return true; - } - - function _permissionOf( - address _owner - ) internal view returns (uint256 permission) { - return permissions[_owner]; - } - - function _permissionRequire( - uint256 _permission, - uint256 _required - ) internal pure returns (bool isPermissioned) { - return _required == _permission & _required; - } - - function _hasPermission( - address _owner, - address _actor, - uint256 _required - ) internal view returns (bool isPermissioned) { - return - _permissionRequire( - _permissionOf(_actor) | _delegated(_owner, _actor), - _required - ); - } - - function _delegated( - address _owner, - address _delegatee - ) internal view returns (uint256 permission) { - // Delegated permission can't be the superset of owner's permission - return - delegations[_uniqueKey(_owner, _delegatee)] & permissions[_owner]; - } -} diff --git a/assets/eip-6366/contracts/EIP6366Meta.sol b/assets/eip-6366/contracts/EIP6366Meta.sol deleted file mode 100644 index d8c98ea..0000000 --- a/assets/eip-6366/contracts/EIP6366Meta.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; -import "./interfaces/IEIP6366Meta.sol"; -import "./interfaces/IEIP6366Error.sol"; - -/** - * @dev Implement the metadata of EIP-6366 - */ -contract EIP6366Meta is IEIP6366Meta { - /** - * @dev Name of permission token - */ - string private tname; - - /** - * @dev Symbol of permission token - */ - string private tsymbol; - - /** - * @dev Mapping permission value to permission's name - */ - mapping(uint256 => string) private permissionNames; - - /** - * @dev Mapping permission value to permission's description - */ - mapping(uint256 => string) private permissionDescriptions; - - /** - * @dev Constructor of permission token - */ - constructor(string memory _name, string memory _symbol) { - tname = _name; - tsymbol = _symbol; - } - - /** - * Get the name of permission token - */ - function name() external view virtual override returns (string memory) { - return tname; - } - - /** - * Get symbol of permission token - */ - function symbol() external view virtual override returns (string memory) { - return tsymbol; - } - - /** - * @dev Get permission's description by value - * @param _permission Value of the permission - */ - function getDescription( - uint256 _permission - ) - external - view - virtual - override - returns (PermissionDescription memory description) - { - return _getDescription(_permission); - } - - /** - * @dev Set the description of given permission - * @param _permission Value of the permission - * @param _name Name of the permission - * @param _description Description of the permission - */ - function setDescription( - uint256 _permission, - string memory _name, - string memory _description - ) external virtual override returns (bool) { - // This method is empty, you should override this in your implement - } - - function _getDescription( - uint256 _permission - ) internal view returns (PermissionDescription memory description) { - return - PermissionDescription({ - permission: _permission, - name: permissionNames[_permission], - description: permissionDescriptions[_permission] - }); - } - - function _setDescription( - uint256 _permission, - string memory _name, - string memory _description - ) internal returns (bool success) { - permissionNames[_permission] = _name; - permissionDescriptions[_permission] = _description; - return true; - } -} diff --git a/assets/eip-6366/contracts/interfaces/IEIP6366Core.sol b/assets/eip-6366/contracts/interfaces/IEIP6366Core.sol deleted file mode 100644 index c9abc6a..0000000 --- a/assets/eip-6366/contracts/interfaces/IEIP6366Core.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; - -/** - * @dev Defined the interface of the core of EIP6366 that MUST to be implemented - */ -interface IEIP6366Core { - event Transfer( - address indexed _from, - address indexed _to, - uint256 indexed _permission - ); - - event Approval( - address indexed _owner, - address indexed _delegatee, - uint256 indexed _permission - ); - - function transfer( - address _to, - uint256 _permission - ) external returns (bool success); - - function approve( - address _delegatee, - uint256 _permission - ) external returns (bool success); - - function permissionOf( - address _owner - ) external view returns (uint256 permission); - - function permissionRequire( - uint256 _permission, - uint256 _required - ) external view returns (bool isPermissioned); - - function hasPermission( - address _owner, - address _actor, - uint256 _required - ) external view returns (bool isPermissioned); - - function delegated( - address _owner, - address _delegatee - ) external view returns (uint256 permission); -} diff --git a/assets/eip-6366/contracts/interfaces/IEIP6366Error.sol b/assets/eip-6366/contracts/interfaces/IEIP6366Error.sol deleted file mode 100644 index 7c9d534..0000000 --- a/assets/eip-6366/contracts/interfaces/IEIP6366Error.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; - -/** - * @dev Define all possible errors that's RECOMMENDED to be implemented - */ -interface IEIP6366Error { - error AccessDenied(address _owner, address _actor, uint256 _permission); - - error DuplicatedPermission(uint256 _permission); - - error OutOfRange(); -} diff --git a/assets/eip-6366/contracts/interfaces/IEIP6366Meta.sol b/assets/eip-6366/contracts/interfaces/IEIP6366Meta.sol deleted file mode 100644 index 3adbf48..0000000 --- a/assets/eip-6366/contracts/interfaces/IEIP6366Meta.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; - -/** - * @dev Defined the interface of the metadata of EIP6366, SHOULD NOT expect to be implemented - */ -interface IEIP6366Meta { - struct PermissionDescription { - uint256 permission; - string name; - string description; - } - - event UpdatePermissionDescription( - uint256 indexed _permission, - string indexed _name, - string indexed _description - ); - - function name() external view returns (string memory); - - function symbol() external view returns (string memory); - - function getDescription( - uint256 _permission - ) external view returns (PermissionDescription memory description); - - function setDescription( - uint256 _permission, - string memory _name, - string memory _description - ) external returns (bool success); -} diff --git a/assets/eip-6366/example/AEcosystem.sol b/assets/eip-6366/example/AEcosystem.sol deleted file mode 100644 index 5f662b0..0000000 --- a/assets/eip-6366/example/AEcosystem.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; -import "./APermissioned.sol"; - -contract AEcosystem is APermissioned { - constructor(address _permsionToken) APermissioned(_permsionToken) { - // Constructor code - } - - function createProposal( - address _permissionOwner - ) external notBlacklisted allow(_permissionOwner, PERMISSION_CREATE) { - // Only allow owner or delegatee with PERMISSION_CREATE - } - - function vote() external notBlacklisted allowOwner(PERMISSION_VOTE) { - // Only allow permission owner with PERMISSION_VOTE - } - - function execute() external notBlacklisted allowOwner(ROLE_OPERATOR) { - // Only allow permission owner with ROLE_OPERATOR - } - - function stopProposal() external notBlacklisted allowOwner(ROLE_ADMIN) { - // Only allow permission owner with ROLE_ADMIN - } - - function register() external notBlacklisted { - // Permission Token is not only provide the ability to whitelist an address - // but also provide the ability to blacklist an address. - // In this case, blacklisted address wont able to register - } -} diff --git a/assets/eip-6366/example/APermissionToken.sol b/assets/eip-6366/example/APermissionToken.sol deleted file mode 100644 index c6b09c3..0000000 --- a/assets/eip-6366/example/APermissionToken.sol +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; -import "../contracts/EIP6366Core.sol"; -import "../contracts/EIP6366Meta.sol"; -import "../contracts/interfaces/IEIP6366Error.sol"; - -/** - * @dev An example for mintable permission token - */ -contract APermissionToken is EIP6366Core, EIP6366Meta { - /** - * @dev Blacklisted - */ - uint256 private constant PERMISSION_DENIED = 2 ** 0; - - /** - * @dev Permission to vote - */ - uint256 internal constant PERMISSION_VOTE = 2 ** 1; - - /** - * @dev Permission to transfer permission token - */ - uint256 internal constant PERMISSION_TRANSFER = 2 ** 2; - - /** - * @dev Permission to execute - */ - uint256 internal constant PERMISSION_EXECUTE = 2 ** 3; - - /** - * @dev Permission to create - */ - uint256 internal constant PERMISSION_CREATE = 2 ** 4; - - /** - * @dev Admin role - */ - uint256 internal constant ROLE_ADMIN = - PERMISSION_VOTE | PERMISSION_EXECUTE | PERMISSION_CREATE; - - /** - * @dev Operator role - */ - uint256 internal constant ROLE_OPERATOR = - PERMISSION_EXECUTE | PERMISSION_VOTE; - - /** - * @dev Permission to manage permission token - */ - uint256 private constant PERMISSION_MASTER = 2 ** 255; - - /** - * @dev Checking for require permissioned from the actor - */ - modifier allow(uint256 required) { - address owner = msg.sender; - if (!_permissionRequire(_permissionOf(owner), required)) { - revert IEIP6366Error.AccessDenied(owner, owner, required); - } - _; - } - - /** - * @dev Deny blacklisted address - */ - modifier notBlacklisted() { - if (_permissionRequire(_permissionOf(msg.sender), PERMISSION_DENIED)) { - revert IEIP6366Error.AccessDenied( - msg.sender, - msg.sender, - PERMISSION_DENIED - ); - } - _; - } - - /** - * @dev Construct ERC-6366 - */ - constructor() EIP6366Meta("Ecosystem A Permission Token", "APT") { - _setDescription( - PERMISSION_DENIED, - "PERMISSION_DENIED", - "Blacklisted address" - ); - _setDescription( - PERMISSION_VOTE, - "PERMISSION_VOTE", - "Permission owner can vote" - ); - _setDescription( - PERMISSION_TRANSFER, - "PERMISSION_TRANSFER", - "Permission owner can transfer" - ); - _setDescription( - PERMISSION_EXECUTE, - "PERMISSION_EXECUTE", - "Permission owner can execute" - ); - _setDescription( - PERMISSION_CREATE, - "PERMISSION_CREATE", - "Permission owner can create" - ); - _setDescription( - PERMISSION_MASTER, - "PERMISSION_MASTER", - "Permission owner can mint and update description" - ); - _setDescription( - ROLE_ADMIN, - "ROLE_ADMIN", - "Admin role can vote, execute and create" - ); - _setDescription( - ROLE_OPERATOR, - "ROLE_OPERATOR", - "Operator role can execute and vote" - ); - - // Assign master permission to deployer - _mint(msg.sender, PERMISSION_MASTER); - } - - /** - * @dev Mint a set of permission to a given target address - */ - function mint( - address _to, - uint256 _permission - ) external allow(PERMISSION_MASTER) returns (bool result) { - return _mint(_to, _permission); - } - - /** - * @dev Burn all permission of a given target address - */ - function burn( - address _to - ) external allow(PERMISSION_MASTER) returns (bool result) { - return _burn(_to); - } - - /** - * @dev Set the description of given index - * @param _index Description's index - * @param _name Name of the permission - * @param _description Description of the permission - */ - function setDescription( - uint256 _index, - string memory _name, - string memory _description - ) external virtual override allow(PERMISSION_MASTER) returns (bool) { - // This method is empty, you should override this in your implement - } - - /** - * @dev Transfer a subset of permission to a given target address - */ - function transfer( - address _to, - uint256 _permission - ) - external - override - allow(PERMISSION_TRANSFER) - notBlacklisted - returns (bool result) - { - return _transfer(_to, _permission); - } -} diff --git a/assets/eip-6366/example/APermissioned.sol b/assets/eip-6366/example/APermissioned.sol deleted file mode 100644 index ee05d69..0000000 --- a/assets/eip-6366/example/APermissioned.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; -import "../contracts/interfaces/IEIP6366Core.sol"; -import "../contracts/interfaces/IEIP6366Error.sol"; - -/** - * @dev Centralized definition of all possible permissions and roles - */ -contract APermissioned { - IEIP6366Core private opt; - - /** - * @dev No permission - */ - uint256 internal constant PERMISSION_NONE = 0; - - /** - * @dev Blacklisted - */ - uint256 internal constant PERMISSION_DENIED = 2 ** 0; - - /** - * @dev Permission to vote - */ - uint256 internal constant PERMISSION_VOTE = 2 ** 1; - - /** - * @dev Permission to transfer permission token - */ - uint256 internal constant PERMISSION_TRANSFER = 2 ** 2; - - /** - * @dev Permission to execute - */ - uint256 internal constant PERMISSION_EXECUTE = 2 ** 3; - - /** - * @dev Permission to create - */ - uint256 internal constant PERMISSION_CREATE = 2 ** 4; - - /** - * @dev Admin role - */ - uint256 internal constant ROLE_ADMIN = - PERMISSION_VOTE | PERMISSION_EXECUTE | PERMISSION_CREATE; - - /** - * @dev Operator role - */ - uint256 internal constant ROLE_OPERATOR = - PERMISSION_EXECUTE | PERMISSION_VOTE; - - /** - * @dev Allow the actor who has required permission - */ - modifier allowOwner(uint256 _required) { - if (!opt.permissionRequire(opt.permissionOf(msg.sender), _required)) { - revert IEIP6366Error.AccessDenied( - msg.sender, - msg.sender, - _required - ); - } - _; - } - - /** - * @dev Deny blacklisted address - */ - modifier notBlacklisted() { - if ( - opt.permissionRequire( - opt.permissionOf(msg.sender), - PERMISSION_DENIED - ) - ) { - revert IEIP6366Error.AccessDenied( - msg.sender, - msg.sender, - PERMISSION_DENIED - ); - } - _; - } - - /** - * @dev Allow permission owner or delegatee - */ - modifier allow(address _owner, uint256 _required) { - // The actor should be the permission owner or delegatee - if (!opt.hasPermission(_owner, msg.sender, _required)) { - revert IEIP6366Error.AccessDenied(_owner, msg.sender, _required); - } - _; - } - - /** - * @dev Constructor - */ - constructor(address _opt) { - opt = IEIP6366Core(_opt); - } -} diff --git a/assets/eip-6381/contracts/EmotableRepository.sol b/assets/eip-6381/contracts/EmotableRepository.sol deleted file mode 100644 index d66c36b..0000000 --- a/assets/eip-6381/contracts/EmotableRepository.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "./IERC6381.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -contract EmotableRepository is IERC6381 { - // Used to avoid double emoting and control undoing - mapping(address => mapping(address => mapping(uint256 => mapping(bytes4 => uint256)))) - private _emotesUsedByEmoter; // Cheaper than using a bool - mapping(address => mapping(uint256 => mapping(bytes4 => uint256))) - private _emotesPerToken; - - function emoteCountOf( - address collection, - uint256 tokenId, - bytes4 emoji - ) public view returns (uint256) { - return _emotesPerToken[collection][tokenId][emoji]; - } - - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji - ) public view returns (bool) { - return _emotesUsedByEmoter[emoter][collection][tokenId][emoji] == 1; - } - - function emote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state - ) public { - bool currentVal = _emotesUsedByEmoter[msg.sender][collection][tokenId][ - emoji - ] == 1; - if (currentVal != state) { - if (state) { - _emotesPerToken[collection][tokenId][emoji] += 1; - } else { - _emotesPerToken[collection][tokenId][emoji] -= 1; - } - _emotesUsedByEmoter[msg.sender][collection][tokenId][emoji] = state - ? 1 - : 0; - emit Emoted(msg.sender, collection, tokenId, emoji, state); - } - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC6381).interfaceId || - interfaceId == type(IERC165).interfaceId; - } -} \ No newline at end of file diff --git a/assets/eip-6381/contracts/IERC6381.sol b/assets/eip-6381/contracts/IERC6381.sol deleted file mode 100644 index 0d31de0..0000000 --- a/assets/eip-6381/contracts/IERC6381.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -interface IERC6381 { - event Emoted( - address indexed emoter, - address indexed collection, - uint256 indexed tokenId, - bytes4 emoji, - bool on - ); - - function emoteCountOf( - address collection, - uint256 tokenId, - bytes4 emoji - ) external view returns (uint256); - - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji - ) external view returns (bool); - - function emote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state - ) external; -} \ No newline at end of file diff --git a/assets/eip-6381/contracts/mocks/ERC721Mock.sol b/assets/eip-6381/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index 1d450dd..0000000 --- a/assets/eip-6381/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} - - function mint(address to, uint256 amount) public { - _mint(to, amount); - } -} diff --git a/assets/eip-6381/hardhat.config.ts b/assets/eip-6381/hardhat.config.ts deleted file mode 100644 index f1b6fde..0000000 --- a/assets/eip-6381/hardhat.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.16", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6381/package.json b/assets/eip-6381/package.json deleted file mode 100644 index e4c6281..0000000 --- a/assets/eip-6381/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "emotable-repository", - "scripts": { - "test": "yarn typechain && hardhat test", - "typechain": "hardhat typechain", - "prettier": "prettier --write ." - }, - "engines": { - "node": ">=16.0.0" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4" - } -} diff --git a/assets/eip-6381/test/emotableRepository.ts b/assets/eip-6381/test/emotableRepository.ts deleted file mode 100644 index 11dd7d2..0000000 --- a/assets/eip-6381/test/emotableRepository.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { BigNumber, Contract } from "ethers"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ERC721Mock, EmotableRepository } from "../typechain-types"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -async function tokenFixture() { - const factory = await ethers.getContractFactory("ERC721Mock"); - const token = await factory.deploy("Chunky", "CHNK"); - await token.deployed(); - - return token; -} - -async function emotableRepositoryFixture() { - const factory = await ethers.getContractFactory("EmotableRepository"); - const repository = await factory.deploy(); - await repository.deployed(); - - return repository; -} - -describe("RMRKEmotableRepositoryMock", async function () { - let token: ERC721Mock; - let repository: EmotableRepository; - let owner: SignerWithAddress; - let addrs: SignerWithAddress[]; - const tokenId = bn(1); - const emoji1 = Buffer.from("😎"); - const emoji2 = Buffer.from("😁"); - - beforeEach(async function () { - [owner, ...addrs] = await ethers.getSigners(); - token = await loadFixture(tokenFixture); - repository = await loadFixture(emotableRepositoryFixture); - }); - - it("can support IEmotableRepository", async function () { - expect(await repository.supportsInterface("0x08eb97a6")).to.equal(true); - }); - - it("can support IERC165", async function () { - expect(await repository.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("does not support other interfaces", async function () { - expect(await repository.supportsInterface("0xffffffff")).to.equal(false); - }); - - describe("With minted tokens", async function () { - beforeEach(async function () { - await token.mint(owner.address, tokenId); - }); - - it("can emote", async function () { - await expect( - repository.connect(addrs[0]).emote(token.address, tokenId, emoji1, true) - ) - .to.emit(repository, "Emoted") - .withArgs( - addrs[0].address, - token.address, - tokenId.toNumber(), - emoji1, - true - ); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - }); - - it("can undo emote", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - - await expect(repository.emote(token.address, tokenId, emoji1, false)) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - false - ); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(0)); - }); - - it("can be emoted from different accounts", async function () { - await repository - .connect(addrs[0]) - .emote(token.address, tokenId, emoji1, true); - await repository - .connect(addrs[1]) - .emote(token.address, tokenId, emoji1, true); - await repository - .connect(addrs[2]) - .emote(token.address, tokenId, emoji2, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(2)); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(1)); - }); - - it("can add multiple emojis to same NFT", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - await repository.emote(token.address, tokenId, emoji2, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(1)); - }); - - it("does nothing if new state is the same as old state", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - await repository.emote(token.address, tokenId, emoji1, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - - await repository.emote(token.address, tokenId, emoji2, false); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(0)); - }); - }); -}); diff --git a/assets/eip-6384/implementation/src/IEvalEIP712Buffer.sol b/assets/eip-6384/implementation/src/IEvalEIP712Buffer.sol deleted file mode 100644 index 2fcaf29..0000000 --- a/assets/eip-6384/implementation/src/IEvalEIP712Buffer.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -interface IEvalEIP712Buffer { - function evalEIP712Buffer(bytes32 domainHash, string memory primaryType, bytes memory typedDataBuffer) - external - view - returns (string[] memory); -} diff --git a/assets/eip-6384/implementation/src/MyToken/MyToken.sol b/assets/eip-6384/implementation/src/MyToken/MyToken.sol deleted file mode 100644 index f3d479b..0000000 --- a/assets/eip-6384/implementation/src/MyToken/MyToken.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {IEvalEIP712Buffer} from "../IEvalEIP712Buffer.sol"; -import {MyToken712ParserHelper} from "./MyToken712ParserHelper.sol"; -import {TransferParameters} from "./MyTokenStructs.sol"; - -contract MyToken is ERC20, EIP712, IEvalEIP712Buffer { - mapping(address => uint256) private _nonces; - address public eip712TransalatorContract; - - bytes32 private constant TYPE_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - bytes32 private constant TRANSFER_TYPEHASH = - keccak256("Transfer(address from,address to,uint256 amount,uint256 nonce,uint256 deadline)"); - - constructor(address _eip712Transaltor) ERC20("MyToken", "MT") EIP712("MyToken", "1") { - eip712TransalatorContract = _eip712Transaltor; - _mint(msg.sender, 1e18); - } - - function mintToCaller() public { - _mint(msg.sender, 1e18); - } - - function nonces(address owner) public view returns (uint256) { - return _nonces[owner]; - } - - function transferWithSig(address from, address to, uint256 amount, uint256 deadline, uint8 r, bytes32 v, bytes32 s) - public - { - require(block.timestamp <= deadline, "TransferSig: expired deadline"); - bytes32 structHash = keccak256(abi.encode(TRANSFER_TYPEHASH, from, to, amount, _nonces[from]++, deadline)); - // _hashTypedDataV4 is a helper function from EIP712.sol that gets the strcutHash and uses the domain separator in order to hash the message - bytes32 hash = _hashTypedDataV4(structHash); - address signer = ECDSA.recover(hash, r, v, s); - require(signer == from, "TransferSig: unauthorized"); - _transfer(from, to, amount); - } - - function DOMAIN_SEPARATOR() public view returns (bytes32) { - return _domainSeparatorV4(); - } - - function evalEIP712Buffer( - bytes32 domainSeparator, - string memory primaryType, - bytes memory typedDataBuffer - ) public view override returns (string[] memory) { - require( - keccak256(abi.encodePacked(primaryType)) == keccak256(abi.encodePacked("Transfer")), - "MyToken: invalid primary type" - ); - require(domainSeparator == _domainSeparatorV4(), "MyToken: Invalid domain"); - return MyToken712ParserHelper(eip712TransalatorContract).parseSig(encodedData); - } -} diff --git a/assets/eip-6384/implementation/src/MyToken/MyToken712ParserHelper.sol b/assets/eip-6384/implementation/src/MyToken/MyToken712ParserHelper.sol deleted file mode 100644 index 8b80845..0000000 --- a/assets/eip-6384/implementation/src/MyToken/MyToken712ParserHelper.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "src/MyToken/MyToken.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -contract MyToken712ParserHelper { - string sigMessage = - "This is MyToken transferWithSig message, by signing this message you are authorizing the transfer of MyToken from your account to the recipient account."; - - struct Transfer { - address from; - address to; - uint256 amount; - uint256 nonce; - uint256 deadline; - } - - function parseSig(bytes memory signature) public view returns (string[] memory sigTranslatedMessage) { - Transfer memory transfer = abi.decode(signature, (Transfer)); - sigTranslatedMessage = new string[](3); - sigTranslatedMessage[0] = sigMessage; - sigTranslatedMessage[1] = Strings.toString(transfer.deadline); - sigTranslatedMessage[2] = string( - abi.encodePacked( - "By signing this message you allow ", - Strings.toHexString(transfer.to), - " to transfer ", - Strings.toString(transfer.amount), - " of MyToken from your account." - ) - ); - return sigTranslatedMessage; - } -} diff --git a/assets/eip-6384/implementation/src/MyToken/MyTokenStructs.sol b/assets/eip-6384/implementation/src/MyToken/MyTokenStructs.sol deleted file mode 100644 index a78f3a0..0000000 --- a/assets/eip-6384/implementation/src/MyToken/MyTokenStructs.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -struct TransferParameters { - address from; - address to; - uint256 amount; - uint256 nonce; - uint256 deadline; -} diff --git a/assets/eip-6384/implementation/src/SeaPort/SeaPort712ParserHelper.sol b/assets/eip-6384/implementation/src/SeaPort/SeaPort712ParserHelper.sol deleted file mode 100644 index f548761..0000000 --- a/assets/eip-6384/implementation/src/SeaPort/SeaPort712ParserHelper.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {ItemType, OrderType, OfferItem, ConsiderationItem, OrderComponents} from "src/SeaPort/SeaPortStructs.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -contract SeaPort712ParserHelper { - bytes32 private domainSeperator = keccak256( - abi.encodePacked("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") - ); - - string sigMessage = - "This is a Seaport listing message, mostly used by OpenSea Dapp, be aware of the potential balance changes"; - - struct BalanceOut { - uint256 amount; - address token; - } - - struct BalanceIn { - uint256 amount; - address token; - } - - function getTokenNameByAddress(address _token) private view returns (string memory) { - if (_token == address(0)) { - return "ETH"; - } else { - (bool success, bytes memory returnData) = _token.staticcall(abi.encodeWithSignature("name()")); - if (success && returnData.length > 0) { - return string(returnData); - } else { - return "Unknown"; - } - } - } - - // need to manage array length because of the fact that default array values are 0x0 which represents 'native token' - function getElementIndexInArray(address addressToSearch, uint256 arrayLength, address[] memory visitedAddresses) - private - pure - returns (uint256) - { - for (uint256 i; i < arrayLength; i++) { - if (addressToSearch == visitedAddresses[i]) { - return i; - } - } - return visitedAddresses.length + 1; - } - - function parseSig(bytes memory signature) public view returns (string[] memory sigTranslatedMessage) { - OrderComponents memory order = abi.decode(signature, (OrderComponents)); - BalanceOut[] memory tempBalanceOut = new BalanceOut[](order.offer.length); - BalanceIn[] memory tempBalanceIn = new BalanceIn[](order.consideration.length); - address[] memory outTokenAddresses = new address[](order.offer.length); - address[] memory inTokenAddresses = new address[](order.consideration.length); - - uint256 outLength; - for (uint256 i; i < order.offer.length; i++) { - uint256 index = getElementIndexInArray(order.offer[i].token, outLength, outTokenAddresses); - if (index != outTokenAddresses.length + 1) { - tempBalanceOut[index].amount += order.offer[i].startAmount; - } else { - outTokenAddresses[outLength] = order.offer[i].token; - tempBalanceOut[outLength] = BalanceOut(order.offer[i].startAmount, order.offer[i].token); - outLength++; - } - } - - uint256 inLength; - for (uint256 i; i < order.consideration.length; i++) { - if (order.offerer == order.consideration[i].recipient) { - uint256 index = getElementIndexInArray(order.consideration[i].token, inLength, inTokenAddresses); - if (index != inTokenAddresses.length + 1) { - tempBalanceIn[index].amount += order.consideration[i].startAmount; - } else { - inTokenAddresses[inLength] = order.consideration[i].token; - tempBalanceIn[inLength] = - BalanceIn(order.consideration[i].startAmount, order.consideration[i].token); - inLength++; - } - } - } - - sigTranslatedMessage = new string[](outLength + inLength + 2); - sigTranslatedMessage[0] = sigMessage; - sigTranslatedMessage[1] = - string(abi.encodePacked("The signature is valid until ", Strings.toString(order.endTime))); - for (uint256 i; i < inLength; i++) { - sigTranslatedMessage[i + 2] = string( - abi.encodePacked( - "You will receive ", - Strings.toString(tempBalanceIn[i].amount), - " of ", - getTokenNameByAddress(tempBalanceIn[i].token) - ) - ); - } - - for (uint256 i; i < outLength; i++) { - sigTranslatedMessage[i + inLength + 2] = string( - abi.encodePacked( - "You will send ", - Strings.toString(tempBalanceOut[i].amount), - " of ", - getTokenNameByAddress(tempBalanceOut[i].token) - ) - ); - } - return (sigTranslatedMessage); - } -} diff --git a/assets/eip-6384/implementation/src/SeaPort/SeaPortMock.sol b/assets/eip-6384/implementation/src/SeaPort/SeaPortMock.sol deleted file mode 100644 index 73c374f..0000000 --- a/assets/eip-6384/implementation/src/SeaPort/SeaPortMock.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "src/IEvalEIP712Buffer.sol"; -import {ItemType, OrderType, OfferItem, ConsiderationItem, OrderComponents} from "src/SeaPort/SeaPortStructs.sol"; -import {SeaPort712ParserHelper} from "src/SeaPort/SeaPort712ParserHelper.sol"; - -contract SeaPortMock is IEvalEIP712Buffer { - address public immutable eip712TransalatorContract; - - bytes32 private constant TYPE_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - constructor(address _translator) { - eip712TransalatorContract = _translator; - } - - // SeaPort logic - - function evalEIP712Buffer( - bytes32 domainSeparator, - string memory primaryType, - bytes memory typedDataBuffer - ) public view override returns (string[] memory) { - require( - keccak256(abi.encodePacked(primaryType)) == keccak256(abi.encodePacked("OrderComponents")), - "SeaPortMock: Invalid primary type" - ); - require(domainSeparator == DOMAIN_SEPARATOR(), "SeaPortMock: Invalid domain"); - return SeaPort712ParserHelper(eip712TransalatorContract).parseSig(encodedSignature); - } - - function DOMAIN_SEPARATOR() public view returns (bytes32) { - // prettier-ignore - return keccak256( - abi.encode(TYPE_HASH, keccak256(bytes("Seaport")), keccak256(bytes("1.1")), block.chainid, address(this)) - ); - } -} diff --git a/assets/eip-6384/implementation/src/SeaPort/SeaPortStructs.sol b/assets/eip-6384/implementation/src/SeaPort/SeaPortStructs.sol deleted file mode 100644 index 3d19f6b..0000000 --- a/assets/eip-6384/implementation/src/SeaPort/SeaPortStructs.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -enum ItemType -// 0: ETH on mainnet, MATIC on polygon, etc. -{ - NATIVE, - // 1: ERC20 items (ERC777 and ERC20 analogues could also technically work) - ERC20, - // 2: ERC721 items - ERC721, - // 3: ERC1155 items - ERC1155, - // 4: ERC721 items where a number of tokenIds are supported - ERC721_WITH_CRITERIA, - // 5: ERC1155 items where a number of ids are supported - ERC1155_WITH_CRITERIA -} - -enum OrderType -// 0: no partial fills, anyone can execute -{ - FULL_OPEN, - // 1: partial fills supported, anyone can execute - PARTIAL_OPEN, - // 2: no partial fills, only offerer or zone can execute - FULL_RESTRICTED, - // 3: partial fills supported, only offerer or zone can execute - PARTIAL_RESTRICTED -} - -struct OfferItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; -} - -struct ConsiderationItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; - address payable recipient; -} - -struct OrderComponents { - address offerer; - address zone; - OfferItem[] offer; - ConsiderationItem[] consideration; - OrderType orderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 conduitKey; - uint256 counter; -} diff --git a/assets/eip-6384/implementation/test/MyToken.t.sol b/assets/eip-6384/implementation/test/MyToken.t.sol deleted file mode 100644 index 2d6d750..0000000 --- a/assets/eip-6384/implementation/test/MyToken.t.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import {MyToken, IEvalEIP712Buffer} from "src/MyToken/MyToken.sol"; -import {TransferParameters} from "src/MyToken/MyTokenStructs.sol"; -import {MyToken712ParserHelper} from "src/MyToken/MyToken712ParserHelper.sol"; -import {SigUtils} from "./SigUtils.sol"; - -contract MyTokenTest is Test { - MyToken myToken; - MyToken712ParserHelper myToken712ParserHelper; - SigUtils sigUtils; - uint256 internal ownerPrivateKey; - uint256 internal toPrivateKey; - - address internal owner; - address internal to; - - bytes32 private constant TYPE_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - function setUp() public { - myToken712ParserHelper = new MyToken712ParserHelper(); - myToken = new MyToken(address(myToken712ParserHelper)); - sigUtils = new SigUtils(myToken.DOMAIN_SEPARATOR()); - ownerPrivateKey = 0xA11CE; - toPrivateKey = 0xB0B; - owner = vm.addr(ownerPrivateKey); - to = vm.addr(toPrivateKey); - vm.prank(owner); - myToken.mintToCaller(); - } - - function testNonce() public { - uint256 currentNonce = myToken.nonces(address(this)); - } - - function test_Transfer() public { - TransferParameters memory transfer = generateSigPayload(); - bytes32 digest = sigUtils.getTypedDataHash(transfer); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest); - console.log(myToken.balanceOf(owner)); - - myToken.transferWithSig(transfer.from, transfer.to, transfer.amount, transfer.deadline, v, r, s); - console.log(myToken.balanceOf(owner)); - assertEq(myToken.balanceOf(owner), 0); - } - - function testEvalEIP712BufferTransfer() public view { - //SigUtils.Transfer memory transferPayload = generateSigPayload(); - TransferParameters memory transferPayload = generateSigPayload(); - bytes memory encodedTransfer = abi.encode(transferPayload); - string[] memory translatedSig = myToken.evalEIP712Buffer(myToken.DOMAIN_SEPARATOR(), "Transfer", encodedTransfer); - for (uint256 i = 0; i < translatedSig.length; i++) { - console.log(translatedSig[i]); - } - } - - function generateSigPayload() public view returns (TransferParameters memory transfer) { - transfer = TransferParameters({ - from: owner, - to: to, - amount: myToken.balanceOf(owner), - nonce: myToken.nonces(owner), - deadline: block.timestamp + 1000 - }); - //transfer = My - return transfer; - } -} diff --git a/assets/eip-6384/implementation/test/OrderGenerator.sol b/assets/eip-6384/implementation/test/OrderGenerator.sol deleted file mode 100644 index bb31a20..0000000 --- a/assets/eip-6384/implementation/test/OrderGenerator.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {ItemType, OrderType, OfferItem, ConsiderationItem, OrderComponents} from "src/SeaPort/SeaPortStructs.sol"; - -contract OrderGenerator { - function generateOrder() public view returns (OrderComponents memory) { - OrderComponents memory order; - order.orderType = OrderType.FULL_OPEN; - order.offerer = msg.sender; - order.zone = address(0); - order.startTime = block.timestamp; - order.endTime = block.timestamp + 1000; - order.salt = 100; - order.conduitKey = bytes32(0); - order.counter = 1; - - order.offer = new OfferItem[](1); - order.offer[0].token = 0x696383fc9C5C8568C2E7aF8731279b58B9201394; - order.offer[0].itemType = ItemType.ERC721; - order.offer[0].startAmount = 1; - order.offer[0].endAmount = 1; - order.offer[0].identifierOrCriteria = 9243; - - order.consideration = new ConsiderationItem[](1); - order.consideration[0].itemType = ItemType.NATIVE; - order.consideration[0].token = address(0); - order.consideration[0].identifierOrCriteria = 0; - order.consideration[0].startAmount = 0; - order.consideration[0].endAmount = 0; - order.consideration[0].recipient = payable(msg.sender); - return order; - } -} diff --git a/assets/eip-6384/implementation/test/SeaPort712Parser.t.sol b/assets/eip-6384/implementation/test/SeaPort712Parser.t.sol deleted file mode 100644 index 390fecc..0000000 --- a/assets/eip-6384/implementation/test/SeaPort712Parser.t.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {SeaPort712ParserHelper} from "src/SeaPort/SeaPort712ParserHelper.sol"; -import {IEvalEIP712Buffer, SeaPortMock} from "src/SeaPort/SeaPortMock.sol"; -import "test/OrderGenerator.sol"; -import "forge-std/Test.sol"; - -contract SeaPort712ParserTest is Test, OrderGenerator { - SeaPortMock seaPortMock; - SeaPort712ParserHelper seaPort712ParserHelper; - - function setUp() public { - seaPort712ParserHelper = new SeaPort712ParserHelper(); - seaPortMock = new SeaPortMock(address(seaPort712ParserHelper)); - } - - function testEvalEIP712BufferSeaport() public view { - OrderComponents memory order = generateOrder(); - bytes memory encodedOrder = abi.encode(order); - string[] memory translatedSig = seaPortMock.evalEIP712Buffer(seaPortMock.DOMAIN_SEPARATOR(), "OrderComponents", encodedOrder); - for (uint256 i = 0; i < translatedSig.length; i++) { - console.log(translatedSig[i]); - } - } -} diff --git a/assets/eip-6384/implementation/test/SigUtils.sol b/assets/eip-6384/implementation/test/SigUtils.sol deleted file mode 100644 index 970e215..0000000 --- a/assets/eip-6384/implementation/test/SigUtils.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {TransferParameters} from "src/MyToken/MyTokenStructs.sol"; - -contract SigUtils { - bytes32 internal DOMAIN_SEPARATOR; - - constructor(bytes32 _DOMAIN_SEPARATOR) { - DOMAIN_SEPARATOR = _DOMAIN_SEPARATOR; - } - - // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - //bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; - bytes32 public constant TRANSFER_TYPEHASH = - keccak256("Transfer(address from,address to,uint256 amount,uint256 nonce,uint256 deadline)"); - - // computes the hash of a permit - function getStructHash(TransferParameters memory _transfer) internal pure returns (bytes32) { - return keccak256( - abi.encode( - TRANSFER_TYPEHASH, _transfer.from, _transfer.to, _transfer.amount, _transfer.nonce, _transfer.deadline - ) - ); - } - - // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer - function getTypedDataHash(TransferParameters memory _transfer) public view returns (bytes32) { - return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, getStructHash(_transfer))); - } -} diff --git a/assets/eip-6384/media/MiceyMask-non-compliant.png b/assets/eip-6384/media/MiceyMask-non-compliant.png deleted file mode 100644 index b1cc4bb..0000000 Binary files a/assets/eip-6384/media/MiceyMask-non-compliant.png and /dev/null differ diff --git a/assets/eip-6384/media/ZenGo-EIP-compliant-warning.png b/assets/eip-6384/media/ZenGo-EIP-compliant-warning.png deleted file mode 100644 index fd3c5b1..0000000 Binary files a/assets/eip-6384/media/ZenGo-EIP-compliant-warning.png and /dev/null differ diff --git a/assets/eip-6404/tests/create_transactions.py b/assets/eip-6404/tests/create_transactions.py deleted file mode 100644 index 61ce1ef..0000000 --- a/assets/eip-6404/tests/create_transactions.py +++ /dev/null @@ -1,114 +0,0 @@ -from rlp import decode -from snappy import compress -from eip2718_tx_types import * - -# Use `sign_transactions.py` to re-generate signatures after editing this file. - -encoded_signed_txs = List[ByteList[MAX_BYTES_PER_TRANSACTION], MAX_TRANSACTIONS_PER_PAYLOAD]( - encode(LegacySignedTransaction( - nonce=42, - gasprice=69_123_456_789, - startgas=21_000, - to=bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045'), - value=3_141_592_653, - data=bytes([]), - v=27, - r=0x2e05c6dd5e74db9616802ed1df109f96d0a313373fb02e3354c0391383b2907e, - s=0x46c0489a847f0f9ab491b228eeb1581c21d0a66d5d6b74745fa7a9e8ca769532, - )), - encode(LegacySignedTransaction( - nonce=42, - gasprice=69_123_456_789, - startgas=21_000, - to=bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045'), - value=3_141_592_653, - data=bytes([]), - v=cfg.chain_id * 2 + 36, - r=0x1e3b25952ae32d705fa6907687f90089cf7c34a0b3622e2f4cf13d851dd77153, - s=0x2064d327555377cb853944f2c6e19082ad279c9284b1b035d6dac1042111fce5, - )), - bytes([0x01]) + encode(EIP2930SignedTransaction( - chainId=cfg.chain_id, - nonce=42, - gasPrice=69_123_456_789, - gasLimit=21_000, - to=bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045'), - value=3_141_592_653, - data=bytes([]), - accessList=[], - signatureYParity=0, - signatureR=0x2219f0647af0c6f90e50abef64fe89f3b80ce465e2eae215bfde6cfb06c6f7f4, - signatureS=0x09eba6b8b601ef43b33b16c8952b35507eeba05c9ecf654d9c18c0bb9d95eb36, - )), - bytes([0x02]) + encode(EIP1559SignedTransaction( - chain_id=cfg.chain_id, - nonce=42, - max_priority_fee_per_gas=69_123_456_789, - max_fee_per_gas=69_123_456_789, - gas_limit=21_000, - destination=bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045'), - amount=3_141_592_653, - data=bytes([]), - access_list=[], - signature_y_parity=0, - signature_r=0x08c4fb0d85fe64bc22aa817b298201bb7dcd29750787c28502146df3dfc5d745, - signature_s=0x6c5e521d85a84434c889dc0691e9430fb945691239b4ca7f2fc4b1ce2c595422, - )), - bytes([0x05]) + BlobTransactionNetworkWrapper( - tx=SignedBlobTransaction( - message=BlobTransaction( - chain_id=cfg.chain_id, - nonce=42, - max_priority_fee_per_gas=69_123_456_789, - max_fee_per_gas=69_123_456_789, - gas=21_000, - to=Union[None, ExecutionAddress]( - selector=1, - value=ExecutionAddress(bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045')), - ), - value=3_141_592_653, - data=bytes([]), - access_list=[], - max_fee_per_data_gas=0, - blob_versioned_hashes=[ - VersionedHash(bytes.fromhex('0190be472a05e72a4ae3ee7d166cc1e17defa2ae9c62d7b773833d632ebc9667')) - ], - ), - signature=ECDSASignature( - y_parity=True, - r=0x74caac606a751efabe68b963d228c76359fb26966835b9531ba1c4627a8362a0, - s=0x753beec95b58a8b103db199c24a975b1f2ffb348a060d17b8a2e9fff6d441cde, - ), - ), - blob_kzgs=[ - KZGCommitment(bytes.fromhex('D9DCDA904EEF4B46BEC0435C56324724B93D53A2FF384F128ABA9FCF2AFDEC52DFEE966CE059497999F785B1DFEF898E')), - ], - blobs=[ - Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]( - [BLSFieldElement(i) for i in range(FIELD_ELEMENTS_PER_BLOB)]), - ], - kzg_aggregated_proof=KZGProof(bytes.fromhex('3BD713674FB14B46938A768349ABA11B9A45EE94F50B4EB1BDB90FDFA40BDF59445E12541C2D4179938BC934709BB595')), - ).encode_bytes(), -) - -if __name__ == '__main__': - for tx_index, encoded_signed_tx in enumerate(encoded_signed_txs): - eip2718_type = encoded_signed_tx[0] - if eip2718_type == 0x05: - signed_tx = BlobTransactionNetworkWrapper.decode_bytes(encoded_signed_tx[1:]).tx - tx_hash = compute_eip4844_tx_hash(signed_tx) - encoded_signed_tx = bytes([0x05]) + signed_tx.encode_bytes() - elif eip2718_type == 0x02: - tx_hash = compute_eip1559_tx_hash( - decode(encoded_signed_tx[1:], EIP1559SignedTransaction)) - elif eip2718_type == 0x01: - tx_hash = compute_eip2930_tx_hash( - decode(encoded_signed_tx[1:], EIP2930SignedTransaction)) - elif 0xc0 <= eip2718_type <= 0xfe: - tx_hash = compute_legacy_tx_hash( - decode(encoded_signed_tx, LegacySignedTransaction)) - else: - assert False - print(f'{tx_index} - {len(encoded_signed_tx)} bytes (Snappy: {len(compress(encoded_signed_tx))} bytes)') - print(f'0x{tx_hash.hex()}') - print(encoded_signed_tx[:1024].hex()) diff --git a/assets/eip-6404/tests/eip2718_tx_types.py b/assets/eip-6404/tests/eip2718_tx_types.py deleted file mode 100644 index d2928bf..0000000 --- a/assets/eip-6404/tests/eip2718_tx_types.py +++ /dev/null @@ -1,266 +0,0 @@ -from eth_hash.auto import keccak -from remerkleable.basic import boolean, uint8, uint32, uint64, uint256 -from remerkleable.byte_arrays import ByteList, ByteVector, Bytes32, Bytes48 -from remerkleable.complex import Container, List, Vector -from remerkleable.union import Union -from rlp import encode, Serializable -from rlp.sedes import Binary, CountableList, List as RLPList, big_endian_int, binary - -MAX_BYTES_PER_TRANSACTION = uint64(2**30) -MAX_TRANSACTIONS_PER_PAYLOAD = uint64(2**20) - -class ExecutionConfig(Container): - chain_id: uint256 - -cfg = ExecutionConfig( - chain_id=1_337, -) - -class Hash32(Bytes32): - pass - -class LegacyTransaction(Serializable): - fields = ( - ('nonce', big_endian_int), - ('gasprice', big_endian_int), - ('startgas', big_endian_int), - ('to', Binary(20, 20, allow_empty=True)), - ('value', big_endian_int), - ('data', binary), - ) - -class LegacySignedTransaction(Serializable): - fields = ( - ('nonce', big_endian_int), - ('gasprice', big_endian_int), - ('startgas', big_endian_int), - ('to', Binary(20, 20, allow_empty=True)), - ('value', big_endian_int), - ('data', binary), - ('v', big_endian_int), - ('r', big_endian_int), - ('s', big_endian_int), - ) - -def compute_legacy_sig_hash(signed_tx: LegacySignedTransaction) -> Hash32: - if signed_tx.v not in (27, 28): # EIP-155 - return Hash32(keccak(encode(LegacySignedTransaction( - nonce=signed_tx.nonce, - gasprice=signed_tx.gasprice, - startgas=signed_tx.startgas, - to=signed_tx.to, - value=signed_tx.value, - data=signed_tx.data, - v=(uint256(signed_tx.v) - 35) >> 1, - r=0, - s=0, - )))) - else: - return Hash32(keccak(encode(LegacyTransaction( - nonce=signed_tx.nonce, - gasprice=signed_tx.gasprice, - startgas=signed_tx.startgas, - to=signed_tx.to, - value=signed_tx.value, - data=signed_tx.data, - )))) - -def compute_legacy_tx_hash(signed_tx: LegacySignedTransaction) -> Hash32: - return Hash32(keccak(encode(signed_tx))) - -class EIP2930Transaction(Serializable): - fields = ( - ('chainId', big_endian_int), - ('nonce', big_endian_int), - ('gasPrice', big_endian_int), - ('gasLimit', big_endian_int), - ('to', Binary(20, 20, allow_empty=True)), - ('value', big_endian_int), - ('data', binary), - ('accessList', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32)), - ]))), - ) - -class EIP2930SignedTransaction(Serializable): - fields = ( - ('chainId', big_endian_int), - ('nonce', big_endian_int), - ('gasPrice', big_endian_int), - ('gasLimit', big_endian_int), - ('to', Binary(20, 20, allow_empty=True)), - ('value', big_endian_int), - ('data', binary), - ('accessList', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32)), - ]))), - ('signatureYParity', big_endian_int), - ('signatureR', big_endian_int), - ('signatureS', big_endian_int), - ) - -def compute_eip2930_sig_hash(signed_tx: EIP2930SignedTransaction) -> Hash32: - return Hash32(keccak(bytes([0x01]) + encode(EIP2930Transaction( - chainId=signed_tx.chainId, - nonce=signed_tx.nonce, - gasPrice=signed_tx.gasPrice, - gasLimit=signed_tx.gasLimit, - to=signed_tx.to, - value=signed_tx.value, - data=signed_tx.data, - accessList=signed_tx.accessList, - )))) - -def compute_eip2930_tx_hash(signed_tx: EIP2930SignedTransaction) -> Hash32: - return Hash32(keccak(bytes([0x01]) + encode(signed_tx))) - -class EIP1559Transaction(Serializable): - fields = ( - ('chain_id', big_endian_int), - ('nonce', big_endian_int), - ('max_priority_fee_per_gas', big_endian_int), - ('max_fee_per_gas', big_endian_int), - ('gas_limit', big_endian_int), - ('destination', Binary(20, 20, allow_empty=True)), - ('amount', big_endian_int), - ('data', binary), - ('access_list', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32)), - ]))), - ) - -class EIP1559SignedTransaction(Serializable): - fields = ( - ('chain_id', big_endian_int), - ('nonce', big_endian_int), - ('max_priority_fee_per_gas', big_endian_int), - ('max_fee_per_gas', big_endian_int), - ('gas_limit', big_endian_int), - ('destination', Binary(20, 20, allow_empty=True)), - ('amount', big_endian_int), - ('data', binary), - ('access_list', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32)), - ]))), - ('signature_y_parity', big_endian_int), - ('signature_r', big_endian_int), - ('signature_s', big_endian_int), - ) - -def compute_eip1559_sig_hash(signed_tx: EIP1559SignedTransaction) -> Hash32: - return Hash32(keccak(bytes([0x02]) + encode(EIP1559Transaction( - chain_id=signed_tx.chain_id, - nonce=signed_tx.nonce, - max_priority_fee_per_gas=signed_tx.max_priority_fee_per_gas, - max_fee_per_gas=signed_tx.max_fee_per_gas, - gas_limit=signed_tx.gas_limit, - destination=signed_tx.destination, - amount=signed_tx.amount, - data=signed_tx.data, - access_list=signed_tx.access_list, - )))) - -def compute_eip1559_tx_hash(signed_tx: EIP1559SignedTransaction) -> Hash32: - return Hash32(keccak(bytes([0x02]) + encode(signed_tx))) - -MAX_CALLDATA_SIZE = uint64(2**24) -MAX_ACCESS_LIST_STORAGE_KEYS = uint64(2**24) -MAX_ACCESS_LIST_SIZE = uint64(2**24) -MAX_VERSIONED_HASHES_LIST_SIZE = uint64(2**24) - -class ExecutionAddress(ByteVector[20]): - pass - -class VersionedHash(Bytes32): - pass - -class AccessTuple(Container): - address: ExecutionAddress - storage_keys: List[Hash32, MAX_ACCESS_LIST_STORAGE_KEYS] - -class BlobTransaction(Container): - chain_id: uint256 - nonce: uint64 - max_priority_fee_per_gas: uint256 - max_fee_per_gas: uint256 - gas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - data: ByteList[MAX_CALLDATA_SIZE] - access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] - max_fee_per_data_gas: uint256 - blob_versioned_hashes: List[VersionedHash, MAX_VERSIONED_HASHES_LIST_SIZE] - -class ECDSASignature(Container): - y_parity: boolean - r: uint256 - s: uint256 - -class SignedBlobTransaction(Container): - message: BlobTransaction - signature: ECDSASignature - -def compute_eip4844_sig_hash(signed_tx: SignedBlobTransaction) -> Hash32: - return Hash32(keccak(bytes([0x05]) + signed_tx.message.encode_bytes())) - -def compute_eip4844_tx_hash(signed_tx: SignedBlobTransaction) -> Hash32: - return Hash32(keccak(bytes([0x05]) + signed_tx.encode_bytes())) - -FIELD_ELEMENTS_PER_BLOB=4096 -MAX_TX_WRAP_KZG_COMMITMENTS=uint64(2**12) -LIMIT_BLOBS_PER_TX=uint64(2**12) - -class KZGCommitment(Bytes48): - pass - -class BLSFieldElement(uint256): - pass - -class KZGProof(Bytes48): - pass - -class BlobTransactionNetworkWrapper(Container): - tx: SignedBlobTransaction - blob_kzgs: List[KZGCommitment, MAX_TX_WRAP_KZG_COMMITMENTS] - blobs: List[Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB], LIMIT_BLOBS_PER_TX] - kzg_aggregated_proof: KZGProof - -class DestinationType(uint8): - pass - -DESTINATION_TYPE_REGULAR = DestinationType(0x00) -DESTINATION_TYPE_CREATE = DestinationType(0x01) - -class DestinationAddress(Container): - destination_type: DestinationType - address: ExecutionAddress - -class ContractAddressData(Serializable): - fields = ( - ('tx_from', Binary(20, 20)), - ('nonce', big_endian_int), - ) - -def compute_contract_address(tx_from: ExecutionAddress, nonce: uint64) -> ExecutionAddress: - return ExecutionAddress(keccak(encode(ContractAddressData( - tx_from=tx_from, - nonce=nonce, - )))[12:32]) - -class TransactionLimits(Container): - max_priority_fee_per_gas: uint256 - max_fee_per_gas: uint256 - gas: uint64 - -class TransactionInfo(Container): - tx_index: uint32 - tx_hash: Hash32 - tx_from: ExecutionAddress - nonce: uint64 - tx_to: DestinationAddress - tx_value: uint256 - limits: TransactionLimits diff --git a/assets/eip-6404/tests/normalized/convert_transactions.py b/assets/eip-6404/tests/normalized/convert_transactions.py deleted file mode 100644 index 235a6dc..0000000 --- a/assets/eip-6404/tests/normalized/convert_transactions.py +++ /dev/null @@ -1,208 +0,0 @@ -from rlp import decode -from snappy import compress -from ssz_tx_types import * -from create_transactions import * - -def normalize_signed_transaction(encoded_signed_tx: bytes, cfg: ExecutionConfig) -> Transaction: - eip2718_type = encoded_signed_tx[0] - - if eip2718_type == 0x05: # EIP-4844 - signed_tx = BlobTransactionNetworkWrapper.decode_bytes(encoded_signed_tx[1:]).tx - assert signed_tx.message.chain_id == cfg.chain_id - - signature = ecdsa_pack_signature( - signed_tx.signature.y_parity, - signed_tx.signature.r, - signed_tx.signature.s, - ) - tx_from = ecdsa_recover_tx_from(signature, compute_eip4844_sig_hash(signed_tx)) - match signed_tx.message.to.selector(): - case 1: - tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_REGULAR, - address=signed_tx.message.to.value(), - ) - case 0: - tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_CREATE, - address=compute_contract_address(tx_from, signed_tx.message.nonce), - ) - - return Transaction( - payload=TransactionPayload( - tx_from=tx_from, - nonce=signed_tx.message.nonce, - tx_to=tx_to, - tx_value=signed_tx.message.value, - tx_input=signed_tx.message.data, - limits=TransactionLimits( - max_priority_fee_per_gas=signed_tx.message.max_priority_fee_per_gas, - max_fee_per_gas=signed_tx.message.max_fee_per_gas, - gas=signed_tx.message.gas, - ), - sig_type=TransactionSignatureType( - tx_type=TRANSACTION_TYPE_EIP4844, - ), - signature=signature, - access_list=signed_tx.message.access_list, - blob=Optional[BlobDetails](BlobDetails( - max_fee_per_data_gas=signed_tx.message.max_fee_per_data_gas, - blob_versioned_hashes=signed_tx.message.blob_versioned_hashes, - )), - ), - tx_hash=compute_eip4844_tx_hash(signed_tx), - ) - - if eip2718_type == 0x02: # EIP-1559 - signed_tx = decode(encoded_signed_tx[1:], EIP1559SignedTransaction) - assert signed_tx.chain_id == cfg.chain_id - - assert signed_tx.signature_y_parity in (0, 1) - signature = ecdsa_pack_signature( - signed_tx.signature_y_parity != 0, - signed_tx.signature_r, - signed_tx.signature_s, - ) - tx_from = ecdsa_recover_tx_from(signature, compute_eip1559_sig_hash(signed_tx)) - if len(signed_tx.destination) != 0: - tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_REGULAR, - address=ExecutionAddress(signed_tx.destination), - ) - else: - tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_CREATE, - address=compute_contract_address(tx_from, signed_tx.nonce), - ) - - return Transaction( - payload=TransactionPayload( - tx_from=tx_from, - nonce=signed_tx.nonce, - tx_to=tx_to, - tx_value=signed_tx.amount, - tx_input=signed_tx.data, - limits=TransactionLimits( - max_priority_fee_per_gas=signed_tx.max_priority_fee_per_gas, - max_fee_per_gas=signed_tx.max_fee_per_gas, - gas=signed_tx.gas_limit, - ), - sig_type=TransactionSignatureType( - tx_type=TRANSACTION_TYPE_EIP1559, - ), - signature=signature, - access_list=[AccessTuple( - address=access_tuple[0], - storage_keys=access_tuple[1], - ) for access_tuple in signed_tx.access_list], - ), - tx_hash=compute_eip1559_tx_hash(signed_tx), - ) - - if eip2718_type == 0x01: # EIP-2930 - signed_tx = decode(encoded_signed_tx[1:], EIP2930SignedTransaction) - assert signed_tx.chainId == cfg.chain_id - - assert signed_tx.signatureYParity in (0, 1) - signature = ecdsa_pack_signature( - signed_tx.signatureYParity != 0, - signed_tx.signatureR, - signed_tx.signatureS, - ) - tx_from = ecdsa_recover_tx_from(signature, compute_eip2930_sig_hash(signed_tx)) - if len(signed_tx.to) != 0: - tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_REGULAR, - address=ExecutionAddress(signed_tx.to), - ) - else: - tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_CREATE, - address=compute_contract_address(tx_from, signed_tx.nonce), - ) - - return Transaction( - payload=TransactionPayload( - tx_from=tx_from, - nonce=signed_tx.nonce, - tx_to=tx_to, - tx_value=signed_tx.value, - tx_input=signed_tx.data, - limits=TransactionLimits( - max_priority_fee_per_gas=signed_tx.gasPrice, - max_fee_per_gas=signed_tx.gasPrice, - gas=signed_tx.gasLimit, - ), - sig_type=TransactionSignatureType( - tx_type=TRANSACTION_TYPE_EIP2930, - ), - signature=signature, - access_list=[AccessTuple( - address=access_tuple[0], - storage_keys=access_tuple[1], - ) for access_tuple in signed_tx.accessList], - ), - tx_hash=compute_eip2930_tx_hash(signed_tx), - ) - - if 0xc0 <= eip2718_type <= 0xfe: # Legacy - signed_tx = decode(encoded_signed_tx, LegacySignedTransaction) - - if signed_tx.v not in (27, 28): # EIP-155 - assert signed_tx.v in (2 * cfg.chain_id + 35, 2 * cfg.chain_id + 36) - signature = ecdsa_pack_signature( - ((signed_tx.v & 0x1) == 0), - signed_tx.r, - signed_tx.s, - ) - tx_from = ecdsa_recover_tx_from(signature, compute_legacy_sig_hash(signed_tx)) - if len(signed_tx.to) != 0: - tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_REGULAR, - address=ExecutionAddress(signed_tx.to), - ) - else: - tx_to = DestinationAddress( - destination_Type=DESTINATION_TYPE_CREATE, - address=compute_contract_address(tx_from, signed_tx.nonce), - ) - - return Transaction( - payload=TransactionPayload( - tx_from=tx_from, - nonce=signed_tx.nonce, - tx_to=tx_to, - tx_value=signed_tx.value, - tx_input=signed_tx.data, - limits=TransactionLimits( - max_priority_fee_per_gas=signed_tx.gasprice, - max_fee_per_gas=signed_tx.gasprice, - gas=signed_tx.startgas, - ), - sig_type=TransactionSignatureType( - tx_type=TRANSACTION_TYPE_LEGACY, - no_replay_protection=(signed_tx.v in (27, 28)), - ), - signature=signature, - ), - tx_hash=compute_legacy_tx_hash(signed_tx), - ) - - assert False - -transactions = List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD](*[ - normalize_signed_transaction(encoded_signed_tx, cfg) - for encoded_signed_tx in encoded_signed_txs -]) -transactions_root = transactions.hash_tree_root() - -if __name__ == '__main__': - print('transactions_root') - print(f'0x{transactions_root.hex()}') - - for tx_index in range(len(transactions)): - tx = transactions[tx_index] - encoded = tx.encode_bytes() - print(f'{tx_index} - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(f'0x{tx.tx_hash.hex()}') - print(encoded.hex()) diff --git a/assets/eip-6404/tests/normalized/create_proofs.py b/assets/eip-6404/tests/normalized/create_proofs.py deleted file mode 100644 index 15c8c14..0000000 --- a/assets/eip-6404/tests/normalized/create_proofs.py +++ /dev/null @@ -1,137 +0,0 @@ -from os import mkdir -from shutil import rmtree -from ssz_proof_types import * -from convert_transactions import * - -def create_transaction_proof(transactions: Transactions, tx_index: uint64) -> TransactionProof: - tx = transactions[tx_index] - return TransactionProof( - payload_root=Root(tx.payload.hash_tree_root()), - tx_hash=tx.tx_hash, - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -def create_amount_proof(transactions: Transactions, tx_index: uint64) -> TransactionProof: - tx = transactions[tx_index] - return AmountProof( - tx_from=tx.payload.tx_from, - nonce=tx.payload.nonce, - tx_to=tx.payload.tx_to, - tx_value=tx.payload.tx_value, - multi_branch=[ - tx.payload.get_backing().getter(gindex).merkle_root() - for gindex in AMOUNT_PROOF_HELPER_INDICES - ], - tx_hash=tx.tx_hash, - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -def create_sender_proof(transactions: Transactions, tx_index: uint64) -> TransactionProof: - tx = transactions[tx_index] - return SenderProof( - tx_from=tx.payload.tx_from, - nonce=tx.payload.nonce, - tx_to=tx.payload.tx_to, - tx_value=tx.payload.tx_value, - multi_branch=[ - tx.payload.get_backing().getter(gindex).merkle_root() - for gindex in SENDER_PROOF_HELPER_INDICES - ], - tx_hash=tx.tx_hash, - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -def create_info_proof(transactions: Transactions, tx_index: uint64) -> TransactionProof: - tx = transactions[tx_index] - return InfoProof( - tx_from=tx.payload.tx_from, - nonce=tx.payload.nonce, - tx_to=tx.payload.tx_to, - tx_value=tx.payload.tx_value, - limits=tx.payload.limits, - multi_branch=[ - tx.payload.get_backing().getter(gindex).merkle_root() - for gindex in INFO_PROOF_HELPER_INDICES - ], - tx_hash=tx.tx_hash, - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -transaction_proofs = [ - create_transaction_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] -amount_proofs = [ - create_amount_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] -sender_proofs = [ - create_sender_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] -info_proofs = [ - create_info_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] - -if __name__ == '__main__': - dir = os_path.join(os_path.dirname(os_path.realpath(__file__)), 'proofs') - if os_path.exists(dir) and os_path.isdir(dir): - rmtree(dir) - mkdir(dir) - - print('transactions_root') - print(f'0x{transactions_root.hex()}') - file = open(os_path.join(dir, f'transactions_root.ssz'), 'wb') - file.write(transactions_root) - file.close() - - file = open(os_path.join(dir, f'nil_0.ssz'), 'wb') - file.close() - - for tx_index in range(len(transactions)): - print() - - encoded = transaction_proofs[tx_index].encode_bytes() - print(f'{tx_index} - TransactionProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'transaction_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() - - encoded = amount_proofs[tx_index].encode_bytes() - print(f'{tx_index} - AmountProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'amount_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() - - encoded = sender_proofs[tx_index].encode_bytes() - print(f'{tx_index} - SenderProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'sender_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() - - encoded = info_proofs[tx_index].encode_bytes() - print(f'{tx_index} - InfoProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'info_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() diff --git a/assets/eip-6404/tests/normalized/ssz_proof_types.py b/assets/eip-6404/tests/normalized/ssz_proof_types.py deleted file mode 100644 index bc4518e..0000000 --- a/assets/eip-6404/tests/normalized/ssz_proof_types.py +++ /dev/null @@ -1,74 +0,0 @@ -from remerkleable.basic import uint32 -from remerkleable.complex import Vector -from ssz_tx_types import * -from proof_helpers import * - -# Proof 1: Obtain the sequential `tx_index` within an `ExecutionPayload` for a specific `tx_hash` - -class TransactionProof(Container): - payload_root: Root - tx_hash: Hash32 - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] - -# Proof 2: Proof that a transaction sends a certain minimum amount to a specific destination - -AMOUNT_PROOF_INDICES = [ - get_generalized_index(TransactionPayload, 'tx_from'), # 16 - get_generalized_index(TransactionPayload, 'nonce'), # 17 - get_generalized_index(TransactionPayload, 'tx_to'), # 18 - get_generalized_index(TransactionPayload, 'tx_value'), # 19 -] -AMOUNT_PROOF_HELPER_INDICES = get_helper_indices(AMOUNT_PROOF_INDICES) # [5, 3] - -class AmountProof(Container): - tx_from: ExecutionAddress - nonce: uint64 - tx_to: DestinationAddress - tx_value: uint256 - multi_branch: Vector[Root, len(AMOUNT_PROOF_HELPER_INDICES)] - tx_hash: Hash32 - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] - -# Proof 3: Obtain sender addres who sent a certain minimum amount to a specific destination - -SENDER_PROOF_INDICES = [ - get_generalized_index(TransactionPayload, 'tx_from'), # 16 - get_generalized_index(TransactionPayload, 'nonce'), # 17 - get_generalized_index(TransactionPayload, 'tx_to'), # 18 - get_generalized_index(TransactionPayload, 'tx_value'), # 19 -] -SENDER_PROOF_HELPER_INDICES = get_helper_indices(SENDER_PROOF_INDICES) # [5, 3] - -class SenderProof(Container): - tx_from: ExecutionAddress - nonce: uint64 - tx_to: DestinationAddress - tx_value: uint256 - multi_branch: Vector[Root, len(SENDER_PROOF_HELPER_INDICES)] - tx_hash: Hash32 - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] - -# Proof 4: Obtain transaction info including fees, but no calldata, access lists, or blobs - -INFO_PROOF_INDICES = [ - get_generalized_index(TransactionPayload, 'tx_from'), # 16 - get_generalized_index(TransactionPayload, 'nonce'), # 17 - get_generalized_index(TransactionPayload, 'tx_to'), # 18 - get_generalized_index(TransactionPayload, 'tx_value'), # 19 - get_generalized_index(TransactionPayload, 'limits'), # 21 -] -INFO_PROOF_HELPER_INDICES = get_helper_indices(INFO_PROOF_INDICES) # [20, 11, 3] - -class InfoProof(Container): - tx_from: ExecutionAddress - nonce: uint64 - tx_to: DestinationAddress - tx_value: uint256 - limits: TransactionLimits - multi_branch: Vector[Root, len(INFO_PROOF_HELPER_INDICES)] - tx_hash: Hash32 - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] diff --git a/assets/eip-6404/tests/normalized/ssz_tx_types.py b/assets/eip-6404/tests/normalized/ssz_tx_types.py deleted file mode 100644 index aef602e..0000000 --- a/assets/eip-6404/tests/normalized/ssz_tx_types.py +++ /dev/null @@ -1,88 +0,0 @@ -from os import path as os_path -from sys import path -path.append(os_path.dirname(os_path.dirname(os_path.realpath(__file__)))) -path.append('../../eip-6475') - -from typing import Tuple -from optional import Optional -from eth_hash.auto import keccak -from remerkleable.basic import boolean, uint8, uint64, uint256 -from remerkleable.byte_arrays import ByteList -from remerkleable.complex import Container, List -from secp256k1 import ECDSA, PublicKey -from eip2718_tx_types import ( - MAX_CALLDATA_SIZE, - MAX_ACCESS_LIST_SIZE, - MAX_TRANSACTIONS_PER_PAYLOAD, - MAX_VERSIONED_HASHES_LIST_SIZE, - AccessTuple, - DestinationAddress, - ExecutionAddress, - Hash32, - TransactionLimits, - VersionedHash, -) - -class TransactionType(uint8): - pass - -TRANSACTION_TYPE_LEGACY = TransactionType(0x00) -TRANSACTION_TYPE_EIP2930 = TransactionType(0x01) -TRANSACTION_TYPE_EIP1559 = TransactionType(0x02) -TRANSACTION_TYPE_EIP4844 = TransactionType(0x05) - -MAX_TRANSACTION_SIGNATURE_SIZE = uint64(2**18) - -class TransactionSignatureType(Container): - tx_type: TransactionType # EIP-2718 - no_replay_protection: boolean # EIP-155; `TRANSACTION_TYPE_LEGACY` only - -class TransactionSignature(ByteList[MAX_TRANSACTION_SIGNATURE_SIZE]): - pass - -def ecdsa_pack_signature(y_parity: bool, r: uint256, s: uint256) -> TransactionSignature: - return r.to_bytes(32, 'big') + s.to_bytes(32, 'big') + bytes([0x01 if y_parity else 0]) - -def ecdsa_unpack_signature(signature: TransactionSignature) -> Tuple[boolean, uint256, uint256]: - y_parity = signature[64] != 0 - r = uint256.from_bytes(signature[0:32], 'big') - s = uint256.from_bytes(signature[32:64], 'big') - return (y_parity, r, s) - -def ecdsa_validate_signature(signature: TransactionSignature): - SECP256K1N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 - assert len(signature) == 65 - assert signature[64] in (0, 1) - _, r, s = ecdsa_unpack_signature(signature) - assert 0 < r < SECP256K1N - assert 0 < s < SECP256K1N - -def ecdsa_recover_tx_from(signature: TransactionSignature, sig_hash: Hash32) -> ExecutionAddress: - ecdsa = ECDSA() - recover_sig = ecdsa.ecdsa_recoverable_deserialize(signature[0:64], signature[64]) - public_key = PublicKey(ecdsa.ecdsa_recover(sig_hash, recover_sig, raw=True)) - uncompressed = public_key.serialize(compressed=False) - return ExecutionAddress(keccak(uncompressed)[12:32]) - -class BlobDetails(Container): - max_fee_per_data_gas: uint256 - blob_versioned_hashes: List[VersionedHash, MAX_VERSIONED_HASHES_LIST_SIZE] - -class TransactionPayload(Container): - tx_from: ExecutionAddress - nonce: uint64 - tx_to: DestinationAddress - tx_value: uint256 - tx_input: ByteList[MAX_CALLDATA_SIZE] - limits: TransactionLimits - sig_type: TransactionSignatureType - signature: TransactionSignature - access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] # EIP-2930 - blob: Optional[BlobDetails] # EIP-4844 - -class Transaction(Container): - payload: TransactionPayload - tx_hash: Hash32 - -class Transactions(List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]): - pass diff --git a/assets/eip-6404/tests/normalized/verify_proofs.c b/assets/eip-6404/tests/normalized/verify_proofs.c deleted file mode 100644 index 71daba5..0000000 --- a/assets/eip-6404/tests/normalized/verify_proofs.c +++ /dev/null @@ -1,607 +0,0 @@ -// Only one proof is verified at a time - create with `create_proofs.py` - -// TRANSACTION, AMOUNT, SENDER, INFO -#define PROOF_TYPE INFO -// 0, 1, 2, 3, 4 -#define PROOF_INDEX 4 - -/******************************************************************************* - -prj.conf: -CONFIG_NRF_OBERON=y -CONFIG_TIMING_FUNCTIONS=y - -CMakeLists.txt: -target_link_libraries(app PRIVATE nrfxlib_crypto) - -*******************************************************************************/ - -#include -#include - -#include -#include - -#include - -#if DEBUG -#define debug(...) printk(__VA_ARGS__) -#else -#define debug(...) (void) 0 -#endif - -#define array_count(array) ((size_t)(sizeof(array) / sizeof((array)[0]))) - -#define TX_DEPTH (20) -#define MAX_TRANSACTIONS_PER_PAYLOAD ((uint32_t) 1 << TX_DEPTH) - -typedef uint8_t Bytes20[20]; -typedef uint8_t Bytes32[32]; -typedef Bytes20 ExecutionAddress; -typedef Bytes32 Hash32; -typedef Bytes32 Root; - -typedef enum __attribute__((packed)) { - DESTINATION_TYPE_REGULAR = 0x00, - DESTINATION_TYPE_CREATE = 0x01 -} DestinationType; - -typedef struct { - DestinationType destination_type; - ExecutionAddress address; -} DestinationAddress; - -typedef struct { - Bytes32 max_priority_fee_per_gas; - Bytes32 max_fee_per_gas; - uint64_t gas; -} TransactionLimits; - -typedef struct { - uint32_t tx_index; - Hash32 tx_hash; - ExecutionAddress tx_from; - uint64_t nonce; - DestinationAddress tx_to; - Bytes32 tx_value; - TransactionLimits limits; -} TransactionInfo; - -static const Root zero_hash[] = { - {0} -}; - -typedef struct { - Bytes32 chain_id; -} ExecutionConfig; - -static void hash_combine(Root *root, const Root *a, const Root *b) -{ - ocrypto_sha256_ctx ctx; - ocrypto_sha256_init(&ctx); - ocrypto_sha256_update(&ctx, &(*a)[0], sizeof *a); - ocrypto_sha256_update(&ctx, &(*b)[0], sizeof *b); - ocrypto_sha256_final(&ctx, &(*root)[0]); -} - -#define consume(n) \ - do { \ - proof = (const uint8_t *) proof + (n); \ - num_proof_bytes -= (n); \ - } while (0) - -__attribute__((warn_unused_result)) -static int verify_transaction_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg __attribute__((unused)), - const Root *transactions_root, - const Root *expected_tx_hash) -{ - Root root; - - // payload_root - const Root *payload_root = proof; - if (num_proof_bytes < sizeof *payload_root) return 1; - if (memcmp(payload_root, expected_tx_hash, sizeof *payload_root)) return 1; - consume(sizeof *payload_root); - - // tx_hash - const Root *tx_hash = proof; - if (num_proof_bytes < sizeof *tx_hash) return 1; - consume(sizeof *tx_hash); - - // transaction_root - hash_combine(&root, payload_root, tx_hash); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - consume(sizeof *tx_index); - - // transactions_root - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - if (num_proof_bytes) return 1; - return 0; -} - -__attribute__((warn_unused_result)) -static int verify_amount_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg __attribute__((unused)), - const Root *transactions_root, - const ExecutionAddress *expected_tx_to, - const Bytes32 *expected_tx_value_min) -{ - Root root, scratch[2]; - - // tx_from - const ExecutionAddress *tx_from = proof; - if (num_proof_bytes < sizeof *tx_from) return 1; - consume(sizeof *tx_from); - - // nonce - const uint64_t *nonce = proof; - if (num_proof_bytes < sizeof *nonce) return 1; - consume(sizeof *nonce); - - // tx_to.destination_type - const uint8_t *destination_type = proof; - if (num_proof_bytes < sizeof *destination_type) return 1; - if (*destination_type != DESTINATION_TYPE_REGULAR) return 1; - consume(sizeof *destination_type); - - // tx_to.address - const ExecutionAddress *addr = proof; - if (num_proof_bytes < sizeof *addr) return 1; - if (memcmp(addr, expected_tx_to, sizeof *addr)) return 1; - consume(sizeof *addr); - - // tx_value - const Bytes32 *tx_value = proof; - if (num_proof_bytes < sizeof *tx_value) return 1; - for (int i = sizeof *tx_value - 1; i >= 0; i--) { - if ((*tx_value)[i] > (*expected_tx_value_min)[i]) break; - if ((*tx_value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *tx_value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // payload_root - /* 16 */ memcpy(&root[0], tx_from, 20); memset(&root[20], 0, 12); - /* 17 */ memcpy(&scratch[0][0], nonce, 8); memset(&scratch[0][8], 0, 24); - /* 8 */ hash_combine(&root, &root, &scratch[0]); - /* 36 */ scratch[0][0] = DESTINATION_TYPE_REGULAR; - /* */ memset(&scratch[0][1], 0, 31); - /* 37 */ memcpy(&scratch[1][0], addr, 20); memset(&scratch[1][20], 0, 12); - /* 18 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - /* 9 */ hash_combine(&scratch[0], &scratch[0], tx_value); - /* 4 */ hash_combine(&root, &root, &scratch[0]); - /* 2 */ hash_combine(&root, &root, &multi_branch[0]); - /* 1 */ hash_combine(&root, &root, &multi_branch[1]); - - // tx_hash - const Root *tx_hash = proof; - if (num_proof_bytes < sizeof *tx_hash) return 1; - consume(sizeof *tx_hash); - - // transaction_root - hash_combine(&root, &root, tx_hash); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - consume(sizeof *tx_index); - - // transactions_root - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - if (num_proof_bytes) return 1; - return 0; -} - -__attribute__((warn_unused_result)) -static int verify_sender_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg __attribute__((unused)), - const Root *transactions_root, - const ExecutionAddress *expected_tx_to, - const Bytes32 *expected_tx_value_min, - ExecutionAddress *tx_from) -{ - Root root, scratch[2]; - - // tx_from - const ExecutionAddress *tx_from_ = proof; - if (num_proof_bytes < sizeof *tx_from_) return 1; - memcpy(tx_from, tx_from_, sizeof *tx_from_); - consume(sizeof *tx_from_); - - // nonce - const uint64_t *nonce = proof; - if (num_proof_bytes < sizeof *nonce) return 1; - consume(sizeof *nonce); - - // tx_to.destination_type - const uint8_t *destination_type = proof; - if (num_proof_bytes < sizeof *destination_type) return 1; - if (*destination_type != DESTINATION_TYPE_REGULAR) return 1; - consume(sizeof *destination_type); - - // tx_to.address - const ExecutionAddress *addr = proof; - if (num_proof_bytes < sizeof *addr) return 1; - if (memcmp(addr, expected_tx_to, sizeof *addr)) return 1; - consume(sizeof *addr); - - // tx_value - const Bytes32 *tx_value = proof; - if (num_proof_bytes < sizeof *tx_value) return 1; - for (int i = sizeof *tx_value - 1; i >= 0; i--) { - if ((*tx_value)[i] > (*expected_tx_value_min)[i]) break; - if ((*tx_value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *tx_value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // payload_root - /* 16 */ memcpy(&root[0], tx_from, 20); memset(&root[20], 0, 12); - /* 17 */ memcpy(&scratch[0][0], nonce, 8); memset(&scratch[0][8], 0, 24); - /* 8 */ hash_combine(&root, &root, &scratch[0]); - /* 36 */ scratch[0][0] = DESTINATION_TYPE_REGULAR; - /* */ memset(&scratch[0][1], 0, 31); - /* 37 */ memcpy(&scratch[1][0], addr, 20); memset(&scratch[1][20], 0, 12); - /* 18 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - /* 9 */ hash_combine(&scratch[0], &scratch[0], tx_value); - /* 4 */ hash_combine(&root, &root, &scratch[0]); - /* 2 */ hash_combine(&root, &root, &multi_branch[0]); - /* 1 */ hash_combine(&root, &root, &multi_branch[1]); - - // tx_hash - const Root *tx_hash = proof; - if (num_proof_bytes < sizeof *tx_hash) return 1; - consume(sizeof *tx_hash); - - // transaction_root - hash_combine(&root, &root, tx_hash); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - consume(sizeof *tx_index); - - // transactions_root - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - if (num_proof_bytes) return 1; - return 0; -} - -__attribute__((warn_unused_result)) -static int verify_info_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg __attribute__((unused)), - const Root *transactions_root, - TransactionInfo *info) -{ - memset(info, 0, sizeof *info); - - Root root, scratch[2]; - - // tx_from - const ExecutionAddress *tx_from = proof; - if (num_proof_bytes < sizeof *tx_from) return 1; - memcpy(info->tx_from, tx_from, sizeof *tx_from); - consume(sizeof *tx_from); - - // nonce - const uint64_t *nonce = proof; - if (num_proof_bytes < sizeof *nonce) return 1; - info->nonce = *nonce; - consume(sizeof *nonce); - - // tx_to.destination_type - const uint8_t *destination_type = proof; - if (num_proof_bytes < sizeof *destination_type) return 1; - if (*destination_type > 1) return 1; - info->tx_to.destination_type = *destination_type; - consume(sizeof *destination_type); - - // tx_to.address - const ExecutionAddress *addr = proof; - if (num_proof_bytes < sizeof *addr) return 1; - memcpy(info->tx_to.address, addr, sizeof *addr); - consume(sizeof *addr); - - // tx_value - const Bytes32 *tx_value = proof; - if (num_proof_bytes < sizeof *tx_value) return 1; - memcpy(info->tx_value, tx_value, sizeof *tx_value); - consume(sizeof *tx_value); - - // limits.max_priority_fee_per_gas - const Bytes32 *max_prio = proof; - if (num_proof_bytes < sizeof *max_prio) return 1; - memcpy(info->limits.max_priority_fee_per_gas, max_prio, sizeof *max_prio); - consume(sizeof *max_prio); - - // limits.max_fee_per_gas - const Bytes32 *max_fee = proof; - if (num_proof_bytes < sizeof *max_fee) return 1; - memcpy(info->limits.max_fee_per_gas, max_fee, sizeof *max_fee); - consume(sizeof *max_fee); - - // limits.gas - const uint64_t *gas = proof; - if (num_proof_bytes < sizeof *gas) return 1; - info->limits.gas = *gas; - consume(sizeof *gas); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 3 * sizeof *multi_branch) return 1; - consume(3 * sizeof *multi_branch); - - // payload_root - /* 16 */ memcpy(&root[0], tx_from, 20); memset(&root[20], 0, 12); - /* 17 */ memcpy(&scratch[0][0], nonce, 8); memset(&scratch[0][8], 0, 24); - /* 8 */ hash_combine(&root, &root, &scratch[0]); - /* 36 */ scratch[0][0] = *destination_type; - /* */ memset(&scratch[0][1], 0, 31); - /* 37 */ memcpy(&scratch[1][0], addr, 20); memset(&scratch[1][20], 0, 12); - /* 18 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - /* 9 */ hash_combine(&scratch[0], &scratch[0], tx_value); - /* 4 */ hash_combine(&root, &root, &scratch[0]); - /* 42 */ hash_combine(&scratch[0], max_prio, max_fee); - /* 86 */ memcpy(&scratch[1][0], gas, 8); memset(&scratch[1][8], 0, 24); - /* 43 */ hash_combine(&scratch[1], &scratch[1], &zero_hash[0]); - /* 21 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - /* 10 */ hash_combine(&scratch[0], &multi_branch[0], &scratch[0]); - /* 5 */ hash_combine(&scratch[0], &scratch[0], &multi_branch[1]); - /* 2 */ hash_combine(&root, &root, &scratch[0]); - /* 1 */ hash_combine(&root, &root, &multi_branch[2]); - - // tx_hash - const Root *tx_hash = proof; - if (num_proof_bytes < sizeof *tx_hash) return 1; - memcpy(info->tx_hash, tx_hash, sizeof *tx_hash); - consume(sizeof *tx_hash); - - // transaction_root - hash_combine(&root, &root, &info->tx_hash); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - info->tx_index = *tx_index; - consume(sizeof *tx_index); - - // transactions_root - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - if (num_proof_bytes) return 1; - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// - -#define STR(x) #x -#define QUOTE(x) STR(x) -__asm__ ( - ".section .rodata\n" - ".global transactions_root\n" - "transactions_root:\n" - ".incbin \"proofs/transactions_root.ssz\"\n" - ".global proof\n" - "proof:\n" - ".incbin \"proofs/" QUOTE(PROOF_TYPE) "_" QUOTE(PROOF_INDEX) ".ssz\"\n" - ".global num_proof_bytes\n" - ".set num_proof_bytes, . - proof\n" - ".section .text\n" -); -extern const uint8_t transactions_root[]; -extern const uint8_t proof[]; -extern const uint8_t num_proof_bytes[]; - -const ExecutionConfig cfg = { - .chain_id = { - 0x39, 0x05 - } -}; - -typedef enum { - NIL, - TRANSACTION, - AMOUNT, - SENDER, - INFO -} proof_type; - -void main(void) -{ - k_msleep(1000); - - printk("Normalized %s_%s (%zu bytes)\n", - QUOTE(PROOF_TYPE), QUOTE(PROOF_INDEX), (size_t) num_proof_bytes); - - timing_init(); - timing_start(); - timing_t start_time, end_time; - switch (PROOF_TYPE) { - case NIL: { - } break; - case TRANSACTION: { - const Root *expected_tx_hash = (const Root *) &proof[0]; - start_time = timing_counter_get(); - if (verify_transaction_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - expected_tx_hash)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("tx_index = %u\n", proof[64]); - } break; - case AMOUNT: { - const ExecutionAddress expected_tx_to = { - 0xd8, 0xda, 0x6b, 0xf2, 0x69, 0x64, 0xaf, 0x9d, 0x7e, 0xed, - 0x9e, 0x03, 0xe5, 0x34, 0x15, 0xd3, 0x7a, 0xa9, 0x60, 0x45, - }; - const Bytes32 expected_tx_value_min = { - 0x00, 0xca, 0x9a, 0x3b - }; - start_time = timing_counter_get(); - if (verify_amount_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - &expected_tx_to, &expected_tx_value_min)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("OK\n"); - } break; - case SENDER: { - const ExecutionAddress expected_tx_to = { - 0xd8, 0xda, 0x6b, 0xf2, 0x69, 0x64, 0xaf, 0x9d, 0x7e, 0xed, - 0x9e, 0x03, 0xe5, 0x34, 0x15, 0xd3, 0x7a, 0xa9, 0x60, 0x45, - }; - const Bytes32 expected_tx_value_min = { - 0x00, 0xca, 0x9a, 0x3b - }; - ExecutionAddress tx_from; - start_time = timing_counter_get(); - if (verify_sender_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - &expected_tx_to, &expected_tx_value_min, - &tx_from)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("tx_from = 0x"); - for (size_t i = 0; i < sizeof tx_from; i++) - printk("%02x", tx_from[i]); - printk("\n"); - } break; - case INFO: { - TransactionInfo info; - start_time = timing_counter_get(); - if (verify_info_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - &info)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("info = {\n"); - printk(" tx_index = %lu\n", (unsigned long) info.tx_index); - printk(" tx_hash = 0x"); - for (size_t i = 0; i < sizeof info.tx_hash; i++) - printk("%02x", info.tx_hash[i]); - printk("\n"); - printk(" tx_from = 0x"); - for (size_t i = 0; i < sizeof info.tx_from; i++) - printk("%02x", info.tx_from[i]); - printk("\n"); - printk(" nonce = %llu\n", (unsigned long long) info.nonce); - printk(" tx_to = {\n"); - printk(" destination_type = %u\n", info.tx_to.destination_type); - printk(" address = 0x"); - for (size_t i = 0; i < sizeof info.tx_to.address; i++) - printk("%02x", info.tx_to.address[i]); - printk("\n"); - printk(" }\n"); - printk(" tx_value = 0x"); - for (int i = sizeof info.tx_value - 1; i >= 0; i--) - printk("%02x", info.tx_value[i]); - printk("\n"); - printk(" limits = {\n"); - printk(" max_priority_fee_per_gas = 0x"); - for (int i = sizeof (Bytes32) - 1; i >= 0; i--) - printk("%02x", info.limits.max_priority_fee_per_gas[i]); - printk("\n"); - printk(" max_fee_per_gas = 0x"); - for (int i = sizeof (Bytes32) - 1; i >= 0; i--) - printk("%02x", info.limits.max_fee_per_gas[i]); - printk("\n"); - printk(" gas = %llu\n", (unsigned long long) info.limits.gas); - printk(" }\n"); - printk("}\n"); - } break; - } - uint64_t total_cycles = timing_cycles_get(&start_time, &end_time); - uint64_t total_ns = timing_cycles_to_ns(total_cycles); - printk("cycles = %llu (%llu.%06llu ms)\n", - (unsigned long long) total_cycles, - (unsigned long long) (total_ns / 1000000), - (unsigned long long) (total_ns % 1000000)); - timing_stop(); -} diff --git a/assets/eip-6404/tests/normalized/verify_proofs.py b/assets/eip-6404/tests/normalized/verify_proofs.py deleted file mode 100644 index 34d9d83..0000000 --- a/assets/eip-6404/tests/normalized/verify_proofs.py +++ /dev/null @@ -1,182 +0,0 @@ -from create_proofs import * - -def verify_transaction_proof( - proof: TransactionProof, - cfg: ExecutionConfig, - transactions_root: Root, - expected_tx_hash: Root, -): - assert proof.tx_hash == expected_tx_hash - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - proof.payload_root.hash_tree_root(), - proof.tx_hash.hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 2 + 0, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - -def verify_amount_proof( - proof: AmountProof, - cfg: ExecutionConfig, - transactions_root: Root, - expected_tx_to: ExecutionAddress, - expected_tx_value_min: uint256, -): - assert proof.tx_to.destination_type == DESTINATION_TYPE_REGULAR - assert proof.tx_to.address == expected_tx_to - assert proof.tx_value >= expected_tx_value_min - payload_root = calculate_multi_merkle_root( - [ - proof.tx_from.hash_tree_root(), - proof.nonce.hash_tree_root(), - proof.tx_to.hash_tree_root(), - proof.tx_value.hash_tree_root(), - ], - proof.multi_branch, - AMOUNT_PROOF_INDICES, - AMOUNT_PROOF_HELPER_INDICES, - ) - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - payload_root, - proof.tx_hash.hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 2 + 0, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - -def verify_sender_proof( - proof: SenderProof, - cfg: ExecutionConfig, - transactions_root: Root, - expected_tx_to: ExecutionAddress, - expected_tx_value_min: uint256, -) -> ExecutionAddress: - assert proof.tx_to.destination_type == DESTINATION_TYPE_REGULAR - assert proof.tx_to.address == expected_tx_to - assert proof.tx_value >= expected_tx_value_min - payload_root = calculate_multi_merkle_root( - [ - proof.tx_from.hash_tree_root(), - proof.nonce.hash_tree_root(), - proof.tx_to.hash_tree_root(), - proof.tx_value.hash_tree_root(), - ], - proof.multi_branch, - SENDER_PROOF_INDICES, - SENDER_PROOF_HELPER_INDICES, - ) - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - payload_root, - proof.tx_hash.hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 2 + 0, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - - return proof.tx_from - -def verify_info_proof( - proof: InfoProof, - cfg: ExecutionConfig, - transactions_root: Root, -) -> TransactionInfo: - payload_root = calculate_multi_merkle_root( - [ - proof.tx_from.hash_tree_root(), - proof.nonce.hash_tree_root(), - proof.tx_to.hash_tree_root(), - proof.tx_value.hash_tree_root(), - proof.limits.hash_tree_root(), - ], - proof.multi_branch, - INFO_PROOF_INDICES, - INFO_PROOF_HELPER_INDICES, - ) - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - payload_root, - proof.tx_hash.hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 2 + 0, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - - return TransactionInfo( - tx_index=proof.tx_index, - tx_hash=proof.tx_hash, - tx_from=proof.tx_from, - nonce=proof.nonce, - tx_to=proof.tx_to, - tx_value=proof.tx_value, - limits=proof.limits, - ) - -if __name__ == '__main__': - print('transactions_root') - print(f'0x{transactions_root.hex()}') - - print() - for tx_index in range(len(transaction_proofs)): - print(f'{tx_index} - TransactionProof') - expected_tx_hash = transaction_proofs[tx_index].tx_hash - verify_transaction_proof( - transaction_proofs[tx_index], cfg, transactions_root, - expected_tx_hash) - print(f'tx_index = {transaction_proofs[tx_index].tx_index}') - - print() - for tx_index in range(len(amount_proofs)): - print(f'{tx_index} - AmountProof') - expected_tx_to = ExecutionAddress(bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045')) - expected_tx_value_min = 1_000_000_000 - verify_amount_proof( - amount_proofs[tx_index], cfg, transactions_root, - expected_tx_to, expected_tx_value_min, - ) - print(f'OK') - - print() - for tx_index in range(len(sender_proofs)): - print(f'{tx_index} - SenderProof') - expected_tx_to = ExecutionAddress(bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045')) - expected_tx_value_min = 1_000_000_000 - tx_from = verify_sender_proof( - sender_proofs[tx_index], cfg, transactions_root, - expected_tx_to, expected_tx_value_min, - ) - print(f'tx_from = 0x{tx_from.hex()}') - - print() - for tx_index in range(len(info_proofs)): - print(f'{tx_index} - InfoProof') - info = verify_info_proof( - info_proofs[tx_index], cfg, transactions_root, - ) - print(f'info = {info}') diff --git a/assets/eip-6404/tests/proof_helpers.py b/assets/eip-6404/tests/proof_helpers.py deleted file mode 100644 index 15fef05..0000000 --- a/assets/eip-6404/tests/proof_helpers.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Any, Sequence, Set, Union as PyUnion -from hashlib import sha256 -from remerkleable.basic import uint64 -from remerkleable.byte_arrays import Bytes32 -from remerkleable.core import Path -from remerkleable.tree import Gindex as GeneralizedIndex, gindex_bit_iter - -SSZVariableName = str - -class Root(Bytes32): - pass - -def floorlog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"floorlog2 accepts only positive values, x={x}") - return uint64(x.bit_length() - 1) - -def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: - ssz_path = Path(ssz_class) - for item in path: - ssz_path = ssz_path / item - return GeneralizedIndex(ssz_path.gindex()) - -def build_proof(anchor, leaf_index): - if leaf_index <= 1: - return [] # Nothing to prove / invalid index - node = anchor - proof = [] - # Walk down, top to bottom to the leaf - bit_iter, _ = gindex_bit_iter(leaf_index) - for bit in bit_iter: - # Always take the opposite hand for the proof. - # 1 = right as leaf, thus get left - if bit: - proof.append(node.get_left().merkle_root()) - node = node.get_right() - else: - proof.append(node.get_right().merkle_root()) - node = node.get_left() - - return list(reversed(proof)) - -def hash(x: PyUnion[bytes, bytearray, memoryview]) -> Bytes32: - return Bytes32(sha256(x).digest()) - -def generalized_index_parent(index: GeneralizedIndex) -> GeneralizedIndex: - return GeneralizedIndex(index // 2) - -def generalized_index_sibling(index: GeneralizedIndex) -> GeneralizedIndex: - return GeneralizedIndex(index ^ 1) - -def get_branch_indices(tree_index: GeneralizedIndex) -> Sequence[GeneralizedIndex]: - o = [generalized_index_sibling(tree_index)] - while o[-1] > 1: - o.append(generalized_index_sibling(generalized_index_parent(o[-1]))) - return o[:-1] - -def get_path_indices(tree_index: GeneralizedIndex) -> Sequence[GeneralizedIndex]: - o = [tree_index] - while o[-1] > 1: - o.append(generalized_index_parent(o[-1])) - return o[:-1] - -def get_helper_indices(indices: Sequence[GeneralizedIndex]) -> Sequence[GeneralizedIndex]: - all_helper_indices: Set[GeneralizedIndex] = set() - all_path_indices: Set[GeneralizedIndex] = set() - for index in indices: - all_helper_indices = all_helper_indices.union(set(get_branch_indices(index))) - all_path_indices = all_path_indices.union(set(get_path_indices(index))) - - return sorted(all_helper_indices.difference(all_path_indices), reverse=True) - -def calculate_multi_merkle_root(leaves: Sequence[Bytes32], - proof: Sequence[Bytes32], - indices: Sequence[GeneralizedIndex], - helper_indices: Sequence[GeneralizedIndex]) -> Root: - assert len(leaves) == len(indices) - assert len(proof) == len(helper_indices) - objects = { - **{index: Bytes32(node) for index, node in zip(indices, leaves)}, - **{index: Bytes32(node) for index, node in zip(helper_indices, proof)} - } - keys = sorted(objects.keys(), reverse=True) - pos = 0 - while pos < len(keys): - k = keys[pos] - if k in objects and k ^ 1 in objects and k // 2 not in objects: - objects[GeneralizedIndex(k // 2)] = hash( - objects[GeneralizedIndex((k | 1) ^ 1)] + - objects[GeneralizedIndex(k | 1)] - ) - keys.append(GeneralizedIndex(k // 2)) - pos += 1 - return objects[GeneralizedIndex(1)] diff --git a/assets/eip-6404/tests/sign_transaction.py b/assets/eip-6404/tests/sign_transaction.py deleted file mode 100644 index 7b3fe17..0000000 --- a/assets/eip-6404/tests/sign_transaction.py +++ /dev/null @@ -1,28 +0,0 @@ -from secp256k1 import PrivateKey -from create_transactions import * - -if __name__ == '__main__': - privkey = PrivateKey() - - for tx_index, encoded_signed_tx in enumerate(encoded_signed_txs): - eip2718_type = encoded_signed_tx[0] - if eip2718_type == 0x05: - sig_hash = compute_eip4844_sig_hash( - BlobTransactionNetworkWrapper.decode_bytes(encoded_signed_tx[1:]).tx) - elif eip2718_type == 0x02: - sig_hash = compute_eip1559_sig_hash( - decode(encoded_signed_tx[1:], EIP1559SignedTransaction)) - elif eip2718_type == 0x01: - sig_hash = compute_eip2930_sig_hash( - decode(encoded_signed_tx[1:], EIP2930SignedTransaction)) - elif 0xc0 <= eip2718_type <= 0xfe: - sig_hash = compute_legacy_sig_hash( - decode(encoded_signed_tx, LegacySignedTransaction)) - else: - assert False - raw_sig = privkey.ecdsa_sign_recoverable(sig_hash, raw=True) - sig, y_parity = privkey.ecdsa_recoverable_serialize(raw_sig) - print(f'{tx_index}') - print(f'y_parity = {y_parity}') - print(f'r = 0x{sig[0:32].hex()}') - print(f's = 0x{sig[32:64].hex()}') diff --git a/assets/eip-6404/tests/union/convert_transactions.py b/assets/eip-6404/tests/union/convert_transactions.py deleted file mode 100644 index fc6a97e..0000000 --- a/assets/eip-6404/tests/union/convert_transactions.py +++ /dev/null @@ -1,118 +0,0 @@ -from ssz_tx_types import * -from create_transactions import * - -def normalize_signed_transaction(encoded_signed_tx: bytes, cfg: ExecutionConfig) -> Transaction: - eip2718_type = encoded_signed_tx[0] - - if eip2718_type == 0x05: # EIP-4844 - signed_tx = BlobTransactionNetworkWrapper.decode_bytes(encoded_signed_tx[1:]).tx - - return Transaction( - selector=3, - value=signed_tx, - ) - - if eip2718_type == 0x02: # EIP-1559 - signed_tx = decode(encoded_signed_tx[1:], EIP1559SignedTransaction) - - return Transaction( - selector=2, - value=EIP1559SignedSSZTransaction( - message=EIP1559SSZTransaction( - chain_id=signed_tx.chain_id, - nonce=signed_tx.nonce, - max_priority_fee_per_gas=signed_tx.max_priority_fee_per_gas, - max_fee_per_gas=signed_tx.max_fee_per_gas, - gas_limit=signed_tx.gas_limit, - destination=Union[None, ExecutionAddress]( - selector=1, - value=ExecutionAddress(signed_tx.destination), - ) if len(signed_tx.destination) > 0 else Union[None, ExecutionAddress](), - amount=signed_tx.amount, - data=signed_tx.data, - access_list=[AccessTuple( - address=access_tuple[0], - storage_keys=access_tuple[1], - ) for access_tuple in signed_tx.access_list], - ), - signature=ECDSASignature( - y_parity=signed_tx.signature_y_parity, - r=signed_tx.signature_r, - s=signed_tx.signature_s, - ), - ), - ) - - if eip2718_type == 0x01: # EIP-2930 - signed_tx = decode(encoded_signed_tx[1:], EIP2930SignedTransaction) - - return Transaction( - selector=1, - value=EIP2930SignedSSZTransaction( - message=EIP2930SSZTransaction( - chain_id=signed_tx.chainId, - nonce=signed_tx.nonce, - gas_price=signed_tx.gasPrice, - gas_limit=signed_tx.gasLimit, - to=Union[None, ExecutionAddress]( - selector=1, - value=ExecutionAddress(signed_tx.to), - ) if len(signed_tx.to) > 0 else Union[None, ExecutionAddress](), - value=signed_tx.value, - data=signed_tx.data, - access_list=[AccessTuple( - address=access_tuple[0], - storage_keys=access_tuple[1], - ) for access_tuple in signed_tx.accessList], - ), - signature=ECDSASignature( - y_parity=signed_tx.signatureYParity, - r=signed_tx.signatureR, - s=signed_tx.signatureS, - ), - ), - ) - - if 0xc0 <= eip2718_type <= 0xfe: - signed_tx = decode(encoded_signed_tx, LegacySignedTransaction) - - return Transaction( - selector=0, - value=LegacySignedSSZTransaction( - message=LegacySSZTransaction( - nonce=signed_tx.nonce, - gasprice=signed_tx.gasprice, - startgas=signed_tx.startgas, - to=Union[None, ExecutionAddress]( - selector=1, - value=ExecutionAddress(signed_tx.to), - ) if len(signed_tx.to) > 0 else Union[None, ExecutionAddress](), - value=signed_tx.value, - data=signed_tx.data, - ), - signature=LegacyECDSASignature( - v=signed_tx.v, - r=signed_tx.r, - s=signed_tx.s, - ), - ), - ) - - assert False - -transactions = Transactions(*[ - normalize_signed_transaction(encoded_signed_tx, cfg) - for encoded_signed_tx in encoded_signed_txs -]) -transactions_root = transactions.hash_tree_root() - -if __name__ == '__main__': - print('transactions_root') - print(f'0x{transactions_root.hex()}') - - for tx_index in range(len(transactions)): - tx = transactions[tx_index] - encoded = tx.encode_bytes() - print(f'{tx_index} - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(f'0x{tx.get_backing().getter(2).merkle_root().hex()}') - print(encoded.hex()) diff --git a/assets/eip-6404/tests/union/create_proofs.py b/assets/eip-6404/tests/union/create_proofs.py deleted file mode 100644 index 9bd57df..0000000 --- a/assets/eip-6404/tests/union/create_proofs.py +++ /dev/null @@ -1,291 +0,0 @@ -from os import mkdir -from shutil import rmtree -from ssz_proof_types import * -from convert_transactions import * - -def create_transaction_proof(transactions: Transactions, tx_index: uint64) -> TransactionProof: - tx = transactions[tx_index] - return TransactionProof( - tx_root=Root(tx.value().hash_tree_root()), - tx_selector=tx.selector(), - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -def create_amount_proof(transactions: Transactions, tx_index: uint64) -> AmountProof: - tx = transactions[tx_index] - match tx.selector(): - case 3: - tx_proof = UnionAmountProof( - selector=tx.selector(), - value=EIP4844AmountProof( - gas=tx.value().message.gas, - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP4844_AMOUNT_PROOF_HELPER_INDICES - ], - signature_root=tx.value().signature.hash_tree_root(), - ), - ) - case 2: - tx_proof = UnionAmountProof( - selector=tx.selector(), - value=EIP1559AmountProof( - gas_limit=tx.value().message.gas_limit, - destination=tx.value().message.destination, - amount=tx.value().message.amount, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP1559_AMOUNT_PROOF_HELPER_INDICES - ], - signature_root=tx.value().signature.hash_tree_root(), - ), - ) - case 1: - tx_proof = UnionAmountProof( - selector=tx.selector(), - value=EIP2930AmountProof( - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP2930_AMOUNT_PROOF_HELPER_INDICES - ], - signature_root=tx.value().signature.hash_tree_root(), - ), - ) - case 0: - tx_proof = UnionAmountProof( - selector=tx.selector(), - value=LegacyAmountProof( - startgas=tx.value().message.startgas, - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in LEGACY_AMOUNT_PROOF_HELPER_INDICES - ], - signature_root=tx.value().signature.hash_tree_root(), - ), - ) - return AmountProof( - tx_proof=tx_proof, - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -def create_sender_proof(transactions: Transactions, tx_index: uint64) -> SenderProof: - tx = transactions[tx_index] - match tx.selector(): - case 3: - tx_proof = UnionSenderProof( - selector=tx.selector(), - value=EIP4844SenderProof( - gas=tx.value().message.gas, - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP4844_SENDER_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - case 2: - tx_proof = UnionSenderProof( - selector=tx.selector(), - value=EIP1559SenderProof( - gas_limit=tx.value().message.gas_limit, - destination=tx.value().message.destination, - amount=tx.value().message.amount, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP1559_SENDER_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - case 1: - tx_proof = UnionSenderProof( - selector=tx.selector(), - value=EIP2930SenderProof( - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP2930_SENDER_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - case 0: - tx_proof = UnionSenderProof( - selector=tx.selector(), - value=LegacySenderProof( - startgas=tx.value().message.startgas, - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in LEGACY_SENDER_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - return SenderProof( - tx_proof=tx_proof, - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -def create_info_proof(transactions: Transactions, tx_index: uint64) -> InfoProof: - tx = transactions[tx_index] - match tx.selector(): - case 3: - tx_proof = UnionInfoProof( - selector=tx.selector(), - value=EIP4844InfoProof( - nonce=tx.value().message.nonce, - max_priority_fee_per_gas=tx.value().message.max_priority_fee_per_gas, - max_fee_per_gas=tx.value().message.max_fee_per_gas, - gas=tx.value().message.gas, - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP4844_INFO_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - case 2: - tx_proof = UnionInfoProof( - selector=tx.selector(), - value=EIP1559InfoProof( - nonce=tx.value().message.nonce, - max_priority_fee_per_gas=tx.value().message.max_priority_fee_per_gas, - max_fee_per_gas=tx.value().message.max_fee_per_gas, - gas_limit=tx.value().message.gas_limit, - destination=tx.value().message.destination, - amount=tx.value().message.amount, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP1559_INFO_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - case 1: - tx_proof = UnionInfoProof( - selector=tx.selector(), - value=EIP2930InfoProof( - nonce=tx.value().message.nonce, - gas_price=tx.value().message.gas_price, - gas_limit=tx.value().message.gas_limit, - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in EIP2930_INFO_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - case 0: - tx_proof = UnionInfoProof( - selector=tx.selector(), - value=LegacyInfoProof( - nonce=tx.value().message.nonce, - gasprice=tx.value().message.gasprice, - startgas=tx.value().message.startgas, - to=tx.value().message.to, - value=tx.value().message.value, - multi_branch=[ - tx.value().message.get_backing().getter(gindex).merkle_root() - for gindex in LEGACY_INFO_PROOF_HELPER_INDICES - ], - signature=tx.value().signature, - ), - ) - return InfoProof( - tx_proof=tx_proof, - tx_index=tx_index, - tx_branch=build_proof( - transactions.get_backing(), - MAX_TRANSACTIONS_PER_PAYLOAD * 2 + tx_index, - ), - ) - -transaction_proofs = [ - create_transaction_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] -amount_proofs = [ - create_amount_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] -sender_proofs = [ - create_sender_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] -info_proofs = [ - create_info_proof(transactions, tx_index) - for tx_index in range(len(transactions)) -] - -if __name__ == '__main__': - dir = os_path.join(os_path.dirname(os_path.realpath(__file__)), 'proofs') - if os_path.exists(dir) and os_path.isdir(dir): - rmtree(dir) - mkdir(dir) - - print('transactions_root') - print(f'0x{transactions_root.hex()}') - file = open(os_path.join(dir, f'transactions_root.ssz'), 'wb') - file.write(transactions_root) - file.close() - - file = open(os_path.join(dir, f'nil_0.ssz'), 'wb') - file.close() - - for tx_index in range(len(transactions)): - print() - - encoded = transaction_proofs[tx_index].encode_bytes() - print(f'{tx_index} - TransactionProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'transaction_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() - - encoded = amount_proofs[tx_index].encode_bytes() - print(f'{tx_index} - AmountProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'amount_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() - - encoded = sender_proofs[tx_index].encode_bytes() - print(f'{tx_index} - SenderProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'sender_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() - - encoded = info_proofs[tx_index].encode_bytes() - print(f'{tx_index} - InfoProof - {len(encoded)} bytes (Snappy: {len(compress(encoded))})') - print(encoded.hex()) - file = open(os_path.join(dir, f'info_{tx_index}.ssz'), 'wb') - file.write(encoded) - file.close() diff --git a/assets/eip-6404/tests/union/ssz_proof_types.py b/assets/eip-6404/tests/union/ssz_proof_types.py deleted file mode 100644 index 3c41c59..0000000 --- a/assets/eip-6404/tests/union/ssz_proof_types.py +++ /dev/null @@ -1,248 +0,0 @@ -from remerkleable.basic import uint8, uint32 -from remerkleable.complex import Vector -from ssz_tx_types import * -from proof_helpers import * -from eip2718_tx_types import BlobTransaction - -# Proof 1: Obtain the sequential `tx_index` within an `ExecutionPayload` for a specific `tx_hash` - -class TransactionProof(Container): - tx_root: Root - tx_selector: uint8 - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] - -# Proof 2: Proof that a transaction sends a certain minimum amount to a specific destination - -LEGACY_AMOUNT_PROOF_INDICES = [ - get_generalized_index(LegacySSZTransaction, 'startgas'), # 10 - get_generalized_index(LegacySSZTransaction, 'to'), # 11 - get_generalized_index(LegacySSZTransaction, 'value'), # 12 - GeneralizedIndex(7), -] -LEGACY_AMOUNT_PROOF_HELPER_INDICES = get_helper_indices(LEGACY_AMOUNT_PROOF_INDICES) # [13, 4] - -class LegacyAmountProof(Container): - startgas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(LEGACY_AMOUNT_PROOF_HELPER_INDICES)] - signature_root: Root - -EIP2930_AMOUNT_PROOF_INDICES = [ - get_generalized_index(EIP2930SSZTransaction, 'to'), # 12 - get_generalized_index(EIP2930SSZTransaction, 'value'), # 13 -] -EIP2930_AMOUNT_PROOF_HELPER_INDICES = get_helper_indices(EIP2930_AMOUNT_PROOF_INDICES) # [7, 2] - -class EIP2930AmountProof(Container): - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(EIP2930_AMOUNT_PROOF_HELPER_INDICES)] - signature_root: Root - -EIP1559_AMOUNT_PROOF_INDICES = [ - get_generalized_index(EIP1559SSZTransaction, 'gas_limit'), # 20 - get_generalized_index(EIP1559SSZTransaction, 'destination'), # 21 - get_generalized_index(EIP1559SSZTransaction, 'amount'), # 22 -] -EIP1559_AMOUNT_PROOF_HELPER_INDICES = get_helper_indices(EIP1559_AMOUNT_PROOF_INDICES) # [23, 4, 3] - -class EIP1559AmountProof(Container): - gas_limit: uint64 - destination: Union[None, ExecutionAddress] - amount: uint256 - multi_branch: Vector[Root, len(EIP1559_AMOUNT_PROOF_HELPER_INDICES)] - signature_root: Root - -EIP4844_AMOUNT_PROOF_INDICES = [ - get_generalized_index(BlobTransaction, 'gas'), # 20 - get_generalized_index(BlobTransaction, 'to'), # 21 - get_generalized_index(BlobTransaction, 'value'), # 22 -] -EIP4844_AMOUNT_PROOF_HELPER_INDICES = get_helper_indices(EIP4844_AMOUNT_PROOF_INDICES) # [23, 4, 3] - -class EIP4844AmountProof(Container): - gas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(EIP4844_AMOUNT_PROOF_HELPER_INDICES)] - signature_root: Root - -class UnionAmountProof(Union[ - LegacyAmountProof, - EIP2930AmountProof, - EIP1559AmountProof, - EIP4844AmountProof, -]): - pass - -class AmountProof(Container): - tx_proof: UnionAmountProof - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] - -# Proof 3: Obtain sender addres who sent a certain minimum amount to a specific destination - -LEGACY_SENDER_PROOF_INDICES = [ - get_generalized_index(LegacySSZTransaction, 'startgas'), # 10 - get_generalized_index(LegacySSZTransaction, 'to'), # 11 - get_generalized_index(LegacySSZTransaction, 'value'), # 12 - GeneralizedIndex(7), -] -LEGACY_SENDER_PROOF_HELPER_INDICES = get_helper_indices(LEGACY_SENDER_PROOF_INDICES) # [13, 4] - -class LegacySenderProof(Container): - startgas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(LEGACY_SENDER_PROOF_HELPER_INDICES)] - signature: LegacyECDSASignature - -EIP2930_SENDER_PROOF_INDICES = [ - get_generalized_index(EIP2930SSZTransaction, 'to'), # 12 - get_generalized_index(EIP2930SSZTransaction, 'value'), # 13 -] -EIP2930_SENDER_PROOF_HELPER_INDICES = get_helper_indices(EIP2930_SENDER_PROOF_INDICES) # [7, 2] - -class EIP2930SenderProof(Container): - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(EIP2930_SENDER_PROOF_HELPER_INDICES)] - signature: ECDSASignature - -EIP1559_SENDER_PROOF_INDICES = [ - get_generalized_index(EIP1559SSZTransaction, 'gas_limit'), # 20 - get_generalized_index(EIP1559SSZTransaction, 'destination'), # 21 - get_generalized_index(EIP1559SSZTransaction, 'amount'), # 22 -] -EIP1559_SENDER_PROOF_HELPER_INDICES = get_helper_indices(EIP1559_SENDER_PROOF_INDICES) # [23, 4, 3] - -class EIP1559SenderProof(Container): - gas_limit: uint64 - destination: Union[None, ExecutionAddress] - amount: uint256 - multi_branch: Vector[Root, len(EIP1559_SENDER_PROOF_HELPER_INDICES)] - signature: ECDSASignature - -EIP4844_SENDER_PROOF_INDICES = [ - get_generalized_index(BlobTransaction, 'gas'), # 20 - get_generalized_index(BlobTransaction, 'to'), # 21 - get_generalized_index(BlobTransaction, 'value'), # 22 -] -EIP4844_SENDER_PROOF_HELPER_INDICES = get_helper_indices(EIP4844_SENDER_PROOF_INDICES) # [23, 4, 3] - -class EIP4844SenderProof(Container): - gas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(EIP4844_SENDER_PROOF_HELPER_INDICES)] - signature: ECDSASignature - -class UnionSenderProof(Union[ - LegacySenderProof, - EIP2930SenderProof, - EIP1559SenderProof, - EIP4844SenderProof, -]): - pass - -class SenderProof(Container): - tx_proof: UnionSenderProof - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] - -# Proof 4: Obtain transaction info including fees, but no calldata, access lists, or blobs - -LEGACY_INFO_PROOF_INDICES = [ - get_generalized_index(LegacySSZTransaction, 'nonce'), # 8 - get_generalized_index(LegacySSZTransaction, 'gasprice'), # 9 - get_generalized_index(LegacySSZTransaction, 'startgas'), # 10 - get_generalized_index(LegacySSZTransaction, 'to'), # 11 - get_generalized_index(LegacySSZTransaction, 'value'), # 12 - GeneralizedIndex(7), -] -LEGACY_INFO_PROOF_HELPER_INDICES = get_helper_indices(LEGACY_INFO_PROOF_INDICES) # [13] - -class LegacyInfoProof(Container): - nonce: uint64 - gasprice: uint256 - startgas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(LEGACY_INFO_PROOF_HELPER_INDICES)] - signature: LegacyECDSASignature - -EIP2930_INFO_PROOF_INDICES = [ - get_generalized_index(EIP2930SSZTransaction, 'nonce'), # 9 - get_generalized_index(EIP2930SSZTransaction, 'gas_price'), # 10 - get_generalized_index(EIP2930SSZTransaction, 'gas_limit'), # 11 - get_generalized_index(EIP2930SSZTransaction, 'to'), # 12 - get_generalized_index(EIP2930SSZTransaction, 'value'), # 13 - GeneralizedIndex(8), -] -EIP2930_INFO_PROOF_HELPER_INDICES = get_helper_indices(EIP2930_INFO_PROOF_INDICES) # [7] - -class EIP2930InfoProof(Container): - nonce: uint64 - gas_price: uint256 - gas_limit: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(EIP2930_INFO_PROOF_HELPER_INDICES)] - signature: ECDSASignature - -EIP1559_INFO_PROOF_INDICES = [ - get_generalized_index(EIP1559SSZTransaction, 'nonce'), # 17 - get_generalized_index(EIP1559SSZTransaction, 'max_priority_fee_per_gas'), # 18 - get_generalized_index(EIP1559SSZTransaction, 'max_fee_per_gas'), # 19 - get_generalized_index(EIP1559SSZTransaction, 'gas_limit'), # 20 - get_generalized_index(EIP1559SSZTransaction, 'destination'), # 21 - get_generalized_index(EIP1559SSZTransaction, 'amount'), # 22 - GeneralizedIndex(16), -] -EIP1559_INFO_PROOF_HELPER_INDICES = get_helper_indices(EIP1559_INFO_PROOF_INDICES) # [23, 3] - -class EIP1559InfoProof(Container): - nonce: uint64 - max_priority_fee_per_gas: uint256 - max_fee_per_gas: uint256 - gas_limit: uint64 - destination: Union[None, ExecutionAddress] - amount: uint256 - multi_branch: Vector[Root, len(EIP1559_INFO_PROOF_HELPER_INDICES)] - signature: ECDSASignature - -EIP4844_INFO_PROOF_INDICES = [ - get_generalized_index(BlobTransaction, 'nonce'), # 17 - get_generalized_index(BlobTransaction, 'max_priority_fee_per_gas'), # 18 - get_generalized_index(BlobTransaction, 'max_fee_per_gas'), # 19 - get_generalized_index(BlobTransaction, 'gas'), # 20 - get_generalized_index(BlobTransaction, 'to'), # 21 - get_generalized_index(BlobTransaction, 'value'), # 22 - GeneralizedIndex(16), -] -EIP4844_INFO_PROOF_HELPER_INDICES = get_helper_indices(EIP4844_INFO_PROOF_INDICES) # [23, 3] - -class EIP4844InfoProof(Container): - nonce: uint64 - max_priority_fee_per_gas: uint256 - max_fee_per_gas: uint256 - gas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - multi_branch: Vector[Root, len(EIP4844_INFO_PROOF_HELPER_INDICES)] - signature: ECDSASignature - -class UnionInfoProof(Union[ - LegacyInfoProof, - EIP2930InfoProof, - EIP1559InfoProof, - EIP4844InfoProof, -]): - pass - -class InfoProof(Container): - tx_proof: UnionInfoProof - tx_index: uint32 - tx_branch: Vector[Root, 1 + floorlog2(MAX_TRANSACTIONS_PER_PAYLOAD)] diff --git a/assets/eip-6404/tests/union/ssz_tx_types.py b/assets/eip-6404/tests/union/ssz_tx_types.py deleted file mode 100644 index 4d21648..0000000 --- a/assets/eip-6404/tests/union/ssz_tx_types.py +++ /dev/null @@ -1,74 +0,0 @@ -from os import path as os_path -from sys import path -path.append(os_path.dirname(os_path.dirname(os_path.realpath(__file__)))) - -from remerkleable.basic import uint64, uint256 -from remerkleable.byte_arrays import ByteList -from remerkleable.complex import Container, List -from remerkleable.union import Union -from eip2718_tx_types import ( - MAX_CALLDATA_SIZE, - MAX_ACCESS_LIST_SIZE, - MAX_TRANSACTIONS_PER_PAYLOAD, - AccessTuple, - ECDSASignature, - ExecutionAddress, - SignedBlobTransaction, -) - -class LegacySSZTransaction(Container): - nonce: uint64 - gasprice: uint256 - startgas: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - data: ByteList[MAX_CALLDATA_SIZE] - -class LegacyECDSASignature(Container): - v: uint256 - r: uint256 - s: uint256 - -class LegacySignedSSZTransaction(Container): - message: LegacySSZTransaction - signature: LegacyECDSASignature - -class EIP2930SSZTransaction(Container): - chain_id: uint256 - nonce: uint64 - gas_price: uint256 - gas_limit: uint64 - to: Union[None, ExecutionAddress] - value: uint256 - data: ByteList[MAX_CALLDATA_SIZE] - access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] - -class EIP2930SignedSSZTransaction(Container): - message: EIP2930SSZTransaction - signature: ECDSASignature - -class EIP1559SSZTransaction(Container): - chain_id: uint256 - nonce: uint64 - max_priority_fee_per_gas: uint256 - max_fee_per_gas: uint256 - gas_limit: uint64 - destination: Union[None, ExecutionAddress] - amount: uint256 - data: ByteList[MAX_CALLDATA_SIZE] - access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] - -class EIP1559SignedSSZTransaction(Container): - message: EIP1559SSZTransaction - signature: ECDSASignature - -class Transaction(Union[ - LegacySignedSSZTransaction, - EIP2930SignedSSZTransaction, - EIP1559SignedSSZTransaction, - SignedBlobTransaction, -]): - pass - -class Transactions(List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]): - pass diff --git a/assets/eip-6404/tests/union/verify_proofs.c b/assets/eip-6404/tests/union/verify_proofs.c deleted file mode 100644 index c12f94f..0000000 --- a/assets/eip-6404/tests/union/verify_proofs.c +++ /dev/null @@ -1,1548 +0,0 @@ -// Only one proof is verified at a time - create with `create_proofs.py` - -// TRANSACTION, AMOUNT, SENDER, INFO -#define PROOF_TYPE INFO -// 0, 1, 2, 3, 4 -#define PROOF_INDEX 4 - -/******************************************************************************* - -prj.conf: -CONFIG_MAIN_STACK_SIZE=6144 -CONFIG_NRF_OBERON=y -CONFIG_TIMING_FUNCTIONS=y - -CMakeLists.txt: -zephyr_get_compile_definitions_for_lang_as_string(C definitions) -zephyr_get_compile_options_for_lang_as_string(C options) -zephyr_get_include_directories_for_lang_as_string(C includes) -zephyr_get_system_include_directories_for_lang_as_string(C system_includes) -string(CONCAT external_project_cflags - "${definitions} ${options} ${includes} ${system_includes} " - "-specs=nosys.specs") -include(ExternalProject) -set(secp256k1_prefix ${CMAKE_CURRENT_BINARY_DIR}/libsecp256k1) -ExternalProject_Add( - libsecp256k1 - PREFIX ${secp256k1_prefix} - SOURCE_DIR ${secp256k1_srcdir} - DOWNLOAD_COMMAND cd ${secp256k1_srcdir} && git clean -dfX && - ${secp256k1_srcdir}/autogen.sh - CONFIGURE_COMMAND ${secp256k1_srcdir}/configure - CFLAGS=${external_project_cflags} - --srcdir=${secp256k1_srcdir} - --prefix=${secp256k1_prefix} - --host=arm-zephyr-eabi - --disable-shared - --enable-static - --disable-benchmark - --enable-experimental - --enable-module-recovery - --with-asm=arm - --with-ecmult-window=8 - --with-ecmult-gen-precision=2 - BUILD_COMMAND make - INSTALL_COMMAND make install - BUILD_BYPRODUCTS ${secp256k1_prefix}/lib/libsecp256k1.a) -add_library(secp256k1 STATIC IMPORTED GLOBAL) -add_dependencies(secp256k1 libsecp256k1) -file(MAKE_DIRECTORY ${secp256k1_prefix}/include) -set_target_properties(secp256k1 PROPERTIES - IMPORTED_LOCATION ${secp256k1_prefix}/lib/libsecp256k1.a - INTERFACE_INCLUDE_DIRECTORIES ${secp256k1_prefix}/include) -target_link_libraries(app PRIVATE nrfxlib_crypto secp256k1) - -*******************************************************************************/ - -#include -#include - -#include -#include - -#include -#include -#include - -#if DEBUG -#define debug(...) printk(__VA_ARGS__) -#else -#define debug(...) (void) 0 -#endif - -#define array_count(array) ((size_t)(sizeof(array) / sizeof((array)[0]))) - -#define TX_DEPTH (20) -#define MAX_TRANSACTIONS_PER_PAYLOAD ((uint32_t) 1 << TX_DEPTH) - -typedef uint8_t Bytes20[20]; -typedef uint8_t Bytes32[32]; -typedef Bytes20 ExecutionAddress; -typedef Bytes32 Hash32; -typedef Bytes32 Root; - -typedef enum __attribute__((packed)) { - DESTINATION_TYPE_REGULAR = 0x00, - DESTINATION_TYPE_CREATE = 0x01 -} DestinationType; - -typedef struct { - DestinationType destination_type; - ExecutionAddress address; -} DestinationAddress; - -typedef struct { - Bytes32 max_priority_fee_per_gas; - Bytes32 max_fee_per_gas; - uint64_t gas; -} TransactionLimits; - -typedef struct { - uint32_t tx_index; - Hash32 tx_hash; - ExecutionAddress tx_from; - uint64_t nonce; - DestinationAddress tx_to; - Bytes32 tx_value; - TransactionLimits limits; -} TransactionInfo; - -static const Root zero_hash[] = { - {0}, - { - 0xf5, 0xa5, 0xfd, 0x42, 0xd1, 0x6a, 0x20, 0x30, - 0x27, 0x98, 0xef, 0x6e, 0xd3, 0x09, 0x97, 0x9b, - 0x43, 0x00, 0x3d, 0x23, 0x20, 0xd9, 0xf0, 0xe8, - 0xea, 0x98, 0x31, 0xa9, 0x27, 0x59, 0xfb, 0x4b - } -}; - -typedef struct { - Bytes32 chain_id; -} ExecutionConfig; - -static void hash_combine(Root *root, const Root *a, const Root *b) -{ - ocrypto_sha256_ctx ctx; - ocrypto_sha256_init(&ctx); - ocrypto_sha256_update(&ctx, &(*a)[0], sizeof *a); - ocrypto_sha256_update(&ctx, &(*b)[0], sizeof *b); - ocrypto_sha256_final(&ctx, &(*root)[0]); -} - -#define consume(n) \ - do { \ - proof = (const uint8_t *) proof + (n); \ - num_proof_bytes -= (n); \ - } while (0) - -__attribute__((warn_unused_result)) -static int verify_transaction_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg __attribute__((unused)), - const Root *transactions_root, - const Root *expected_tx_hash) -{ - Root root; - - // tx_root - const Root *tx_root = proof; - if (num_proof_bytes < sizeof *tx_root) return 1; - if (memcmp(tx_root, expected_tx_hash, sizeof *tx_root)) return 1; - consume(sizeof *tx_root); - - // tx_selector - const uint8_t *tx_selector = proof; - if (num_proof_bytes < sizeof *tx_selector) return 1; - consume(sizeof *tx_selector); - - // transaction_root - /* 3 */ root[0] = *tx_selector; memset(&root[1], 0, 31); - /* 1 */ hash_combine(&root, tx_root, &root); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - consume(sizeof *tx_index); - - // transactions_root - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - if (num_proof_bytes) return 1; - return 0; -} - -__attribute__((warn_unused_result)) -static int verify_amount_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg __attribute__((unused)), - const Root *transactions_root, - const ExecutionAddress *expected_tx_to, - const Bytes32 *expected_tx_value_min) -{ - Root root, scratch; - - // &tx_proof - const uint32_t *tx_proof_offset = proof; - if (num_proof_bytes < sizeof *tx_proof_offset) return 1; - if (*tx_proof_offset != 680) return 1; - consume(sizeof *tx_proof_offset); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - consume(sizeof *tx_index); - - // tx_branch - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - // tx_selector - const uint8_t *tx_selector = proof; - if (num_proof_bytes < sizeof *tx_selector) return 1; - consume(sizeof *tx_selector); - - // tx_root - switch (*tx_selector) { - case 3: { - // gas - const uint64_t *gas = proof; - if (num_proof_bytes < sizeof *gas) return 1; - consume(sizeof *gas); - - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 172) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - for (int i = sizeof *value - 1; i >= 0; i--) { - if ((*value)[i] > (*expected_tx_value_min)[i]) break; - if ((*value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 3 * sizeof *multi_branch) return 1; - consume(3 * sizeof *multi_branch); - - // signature_root - const Root *signature_root = proof; - if (num_proof_bytes < sizeof *signature_root) return 1; - consume(sizeof *signature_root); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - if (memcmp(to, expected_tx_to, sizeof *to)) return 1; - consume(sizeof *to); - - // sig_root - if (num_proof_bytes) return 1; - /* 42 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 43 */ scratch[0] = 1; memset(&scratch[1], 0, 31); - /* 21 */ hash_combine(&scratch, &root, &scratch); - /* 20 */ memcpy(&root[0], gas, 8); memset(&root[8], 0, 24); - /* 10 */ hash_combine(&root, &root, &scratch); - /* 11 */ hash_combine(&scratch, value, &multi_branch[0]); - /* 5 */ hash_combine(&root, &root, &scratch); - /* 2 */ hash_combine(&root, &multi_branch[1], &root); - /* 1 */ hash_combine(&root, &root, &multi_branch[2]); - - // tx_root - hash_combine(&root, &root, signature_root); - } break; - case 2: { - // gas_limit - const uint64_t *gas_limit = proof; - if (num_proof_bytes < sizeof *gas_limit) return 1; - consume(sizeof *gas_limit); - - // &destination - const uint32_t *destination_offset = proof; - if (num_proof_bytes < sizeof *destination_offset) return 1; - if (*destination_offset != 172) return 1; - consume(sizeof *destination_offset); - - // amount - const Bytes32 *amount = proof; - if (num_proof_bytes < sizeof *amount) return 1; - for (int i = sizeof *amount - 1; i >= 0; i--) { - if ((*amount)[i] > (*expected_tx_value_min)[i]) break; - if ((*amount)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *amount); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 3 * sizeof *multi_branch) return 1; - consume(3 * sizeof *multi_branch); - - // signature_root - const Root *signature_root = proof; - if (num_proof_bytes < sizeof *signature_root) return 1; - consume(sizeof *signature_root); - - // destination_selector - const uint8_t *destination_selector = proof; - if (num_proof_bytes < sizeof *destination_selector) return 1; - if (*destination_selector != 1) return 1; - consume(sizeof *destination_selector); - - // destination - const ExecutionAddress *dest = proof; - if (num_proof_bytes < sizeof *dest) return 1; - if (memcmp(dest, expected_tx_to, sizeof *dest)) return 1; - consume(sizeof *dest); - - // sig_root - if (num_proof_bytes) return 1; - /* 42 */ memcpy(&root[0], dest, 20); memset(&root[20], 0, 12); - /* 43 */ scratch[0] = 1; memset(&scratch[1], 0, 31); - /* 21 */ hash_combine(&scratch, &root, &scratch); - /* 20 */ memcpy(&root[0], gas_limit, 8); memset(&root[8], 0, 24); - /* 10 */ hash_combine(&root, &root, &scratch); - /* 11 */ hash_combine(&scratch, amount, &multi_branch[0]); - /* 5 */ hash_combine(&root, &root, &scratch); - /* 2 */ hash_combine(&root, &multi_branch[1], &root); - /* 1 */ hash_combine(&root, &root, &multi_branch[2]); - - // tx_root - hash_combine(&root, &root, signature_root); - } break; - case 1: { - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 132) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - for (int i = sizeof *value - 1; i >= 0; i--) { - if ((*value)[i] > (*expected_tx_value_min)[i]) break; - if ((*value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // signature_root - const Root *signature_root = proof; - if (num_proof_bytes < sizeof *signature_root) return 1; - consume(sizeof *signature_root); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - if (memcmp(to, expected_tx_to, sizeof *to)) return 1; - consume(sizeof *to); - - // sig_root - if (num_proof_bytes) return 1; - /* 24 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 25 */ scratch[0] = 1; memset(&scratch[1], 0, 31); - /* 12 */ hash_combine(&root, &root, &scratch); - /* 6 */ hash_combine(&root, &root, value); - /* 3 */ hash_combine(&root, &root, &multi_branch[0]); - /* 1 */ hash_combine(&root, &multi_branch[1], &root); - - // tx_root - hash_combine(&root, &root, signature_root); - } break; - case 0: { - // startgas - const uint64_t *startgas = proof; - if (num_proof_bytes < sizeof *startgas) return 1; - consume(sizeof *startgas); - - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 140) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - for (int i = sizeof *value - 1; i >= 0; i--) { - if ((*value)[i] > (*expected_tx_value_min)[i]) break; - if ((*value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // signature_root - const Root *signature_root = proof; - if (num_proof_bytes < sizeof *signature_root) return 1; - consume(sizeof *signature_root); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - if (memcmp(to, expected_tx_to, sizeof *to)) return 1; - consume(sizeof *to); - - // sig_root - if (num_proof_bytes) return 1; - /* 22 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 23 */ scratch[0] = 1; memset(&scratch[1], 0, 31); - /* 11 */ hash_combine(&scratch, &root, &scratch); - /* 10 */ memcpy(&root[0], startgas, 8); memset(&root[8], 0, 24); - /* 5 */ hash_combine(&root, &root, &scratch); - /* 6 */ hash_combine(&scratch, value, &multi_branch[0]); - /* 3 */ hash_combine(&scratch, &scratch, &zero_hash[1]); - /* 2 */ hash_combine(&root, &multi_branch[1], &root); - /* 1 */ hash_combine(&root, &root, &scratch); - - // tx_root - hash_combine(&root, &root, signature_root); - } break; - default: return 1; - } - - // transaction_root - /* 3 */ scratch[0] = *tx_selector; memset(&scratch[1], 0, 31); - /* 1 */ hash_combine(&root, &root, &scratch); - - // transactions_root - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - - return 0; -} - -__attribute__((warn_unused_result)) -static int verify_sender_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg __attribute__((unused)), - const Root *transactions_root, - const ExecutionAddress *expected_tx_to, - const Bytes32 *expected_tx_value_min, - ExecutionAddress *tx_from) -{ - uint8_t y; - Root root, scratch[2]; - - // &tx_proof - const uint32_t *tx_proof_offset = proof; - if (num_proof_bytes < sizeof *tx_proof_offset) return 1; - if (*tx_proof_offset != 680) return 1; - consume(sizeof *tx_proof_offset); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - consume(sizeof *tx_index); - - // tx_branch - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - // tx_selector - const uint8_t *tx_selector = proof; - if (num_proof_bytes < sizeof *tx_selector) return 1; - consume(sizeof *tx_selector); - - // tx_root - const uint8_t *y_parity; - const Bytes32 *r; - const Bytes32 *s; - switch (*tx_selector) { - case 3: { - // gas - const uint64_t *gas = proof; - if (num_proof_bytes < sizeof *gas) return 1; - consume(sizeof *gas); - - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 205) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - for (int i = sizeof *value - 1; i >= 0; i--) { - if ((*value)[i] > (*expected_tx_value_min)[i]) break; - if ((*value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 3 * sizeof *multi_branch) return 1; - consume(3 * sizeof *multi_branch); - - // signature.y_parity - y_parity = proof; - if (num_proof_bytes < sizeof *y_parity) return 1; - if (*y_parity > 1) return 1; - consume(sizeof *y_parity); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - if (memcmp(to, expected_tx_to, sizeof *to)) return 1; - consume(sizeof *to); - - // sig_root - if (num_proof_bytes) return 1; - /* 42 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 43 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 21 */ hash_combine(&scratch[0], &root, &scratch[0]); - /* 20 */ memcpy(&root[0], gas, 8); memset(&root[8], 0, 24); - /* 10 */ hash_combine(&root, &root, &scratch[0]); - /* 11 */ hash_combine(&scratch[0], value, &multi_branch[0]); - /* 5 */ hash_combine(&root, &root, &scratch[0]); - /* 2 */ hash_combine(&root, &multi_branch[1], &root); - /* 1 */ hash_combine(&root, &root, &multi_branch[2]); - - // signature_root - /* 4 */ scratch[0][0] = *y_parity; memset(&scratch[0][1], 0, 31); - /* 2 */ hash_combine(&scratch[0], &scratch[0], r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - case 2: { - // gas_limit - const uint64_t *gas_limit = proof; - if (num_proof_bytes < sizeof *gas_limit) return 1; - consume(sizeof *gas_limit); - - // &destination - const uint32_t *destination_offset = proof; - if (num_proof_bytes < sizeof *destination_offset) return 1; - if (*destination_offset != 205) return 1; - consume(sizeof *destination_offset); - - // amount - const Bytes32 *amount = proof; - if (num_proof_bytes < sizeof *amount) return 1; - for (int i = sizeof *amount - 1; i >= 0; i--) { - if ((*amount)[i] > (*expected_tx_value_min)[i]) break; - if ((*amount)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *amount); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 3 * sizeof *multi_branch) return 1; - consume(3 * sizeof *multi_branch); - - // signature.y_parity - y_parity = proof; - if (num_proof_bytes < sizeof *y_parity) return 1; - if (*y_parity > 1) return 1; - consume(sizeof *y_parity); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // destination_selector - const uint8_t *destination_selector = proof; - if (num_proof_bytes < sizeof *destination_selector) return 1; - if (*destination_selector != 1) return 1; - consume(sizeof *destination_selector); - - // destination - const ExecutionAddress *dest = proof; - if (num_proof_bytes < sizeof *dest) return 1; - if (memcmp(dest, expected_tx_to, sizeof *dest)) return 1; - consume(sizeof *dest); - - // sig_root - if (num_proof_bytes) return 1; - /* 42 */ memcpy(&root[0], dest, 20); memset(&root[20], 0, 12); - /* 43 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 21 */ hash_combine(&scratch[0], &root, &scratch[0]); - /* 20 */ memcpy(&root[0], gas_limit, 8); memset(&root[8], 0, 24); - /* 10 */ hash_combine(&root, &root, &scratch[0]); - /* 11 */ hash_combine(&scratch[0], amount, &multi_branch[0]); - /* 5 */ hash_combine(&root, &root, &scratch[0]); - /* 2 */ hash_combine(&root, &multi_branch[1], &root); - /* 1 */ hash_combine(&root, &root, &multi_branch[2]); - - // signature_root - /* 4 */ scratch[0][0] = *y_parity; memset(&scratch[0][1], 0, 31); - /* 2 */ hash_combine(&scratch[0], &scratch[0], r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - case 1: { - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 165) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - for (int i = sizeof *value - 1; i >= 0; i--) { - if ((*value)[i] > (*expected_tx_value_min)[i]) break; - if ((*value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // signature.y_parity - y_parity = proof; - if (num_proof_bytes < sizeof *y_parity) return 1; - if (*y_parity > 1) return 1; - consume(sizeof *y_parity); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - if (memcmp(to, expected_tx_to, sizeof *to)) return 1; - consume(sizeof *to); - - // sig_root - if (num_proof_bytes) return 1; - /* 24 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 25 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 12 */ hash_combine(&root, &root, &scratch[0]); - /* 6 */ hash_combine(&root, &root, value); - /* 3 */ hash_combine(&root, &root, &multi_branch[0]); - /* 1 */ hash_combine(&root, &multi_branch[1], &root); - - // signature_root - /* 4 */ scratch[0][0] = *y_parity; memset(&scratch[0][1], 0, 31); - /* 2 */ hash_combine(&scratch[0], &scratch[0], r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - case 0: { - // startgas - const uint64_t *startgas = proof; - if (num_proof_bytes < sizeof *startgas) return 1; - consume(sizeof *startgas); - - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 204) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - for (int i = sizeof *value - 1; i >= 0; i--) { - if ((*value)[i] > (*expected_tx_value_min)[i]) break; - if ((*value)[i] < (*expected_tx_value_min)[i]) return 1; - } - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // signature.v - const Bytes32 *v = proof; - if (num_proof_bytes < sizeof *v) return 1; - y = (((*v)[0] & 0x1) == 0); - y_parity = &y; - consume(sizeof *v); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - if (memcmp(to, expected_tx_to, sizeof *to)) return 1; - consume(sizeof *to); - - // sig_root - if (num_proof_bytes) return 1; - /* 22 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 23 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 11 */ hash_combine(&scratch[0], &root, &scratch[0]); - /* 10 */ memcpy(&root[0], startgas, 8); memset(&root[8], 0, 24); - /* 5 */ hash_combine(&root, &root, &scratch[0]); - /* 6 */ hash_combine(&scratch[0], value, &multi_branch[0]); - /* 3 */ hash_combine(&scratch[0], &scratch[0], &zero_hash[1]); - /* 2 */ hash_combine(&root, &multi_branch[1], &root); - /* 1 */ hash_combine(&root, &root, &scratch[0]); - - // signature_root - /* 2 */ hash_combine(&scratch[0], v, r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - default: return 1; - } - - // tx_from - uint8_t ser_sig[64]; - for (size_t i = 0; i < 32; i++) { - ser_sig[i] = (*r)[31 - i]; - ser_sig[i + 32] = (*s)[31 - i]; - } - secp256k1_ecdsa_recoverable_signature recover_sig; - if (!secp256k1_ecdsa_recoverable_signature_parse_compact( - secp256k1_context_static, &recover_sig, ser_sig, *y_parity)) - { - return 1; - } - secp256k1_pubkey public_key; - if (!secp256k1_ecdsa_recover( - secp256k1_context_static, &public_key, &recover_sig, &root[0])) - { - return 1; - } - uint8_t uncompressed[65]; - size_t n = sizeof uncompressed; - if (!secp256k1_ec_pubkey_serialize( - secp256k1_context_static, uncompressed, &n, - &public_key, SECP256K1_EC_UNCOMPRESSED)) - { - debug("secp256k1_ec_pubkey_serialize failed\n"); - } - if (n != sizeof uncompressed) - debug("secp256k1_ec_pubkey_serialize failed: Length %zu\n", n); - sha3_context ctx; - if (sha3_Init(&ctx, 256)) - debug("sha3_Init failed\n"); - if (sha3_SetFlags(&ctx, SHA3_FLAGS_KECCAK) != SHA3_FLAGS_KECCAK) - debug("sha3_SetFlags failed\n"); - sha3_Update(&ctx, uncompressed, sizeof uncompressed); - memcpy(tx_from, &((const uint8_t *) sha3_Finalize(&ctx))[12], 20); - - // tx_root - hash_combine(&root, &root, &scratch[0]); - - // transaction_root - /* 3 */ scratch[0][0] = *tx_selector; memset(&scratch[0][1], 0, 31); - /* 1 */ hash_combine(&root, &root, &scratch[0]); - - // transactions_root - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - - return 0; -} - -__attribute__((warn_unused_result)) -static int verify_info_proof( - const void *proof, - size_t num_proof_bytes, - const ExecutionConfig *cfg, - const Root *transactions_root, - TransactionInfo *info) -{ - memset(info, 0, sizeof *info); - - uint8_t y; - Root root, scratch[2]; - - // &tx_proof - const uint32_t *tx_proof_offset = proof; - if (num_proof_bytes < sizeof *tx_proof_offset) return 1; - if (*tx_proof_offset != 680) return 1; - consume(sizeof *tx_proof_offset); - - // tx_index - const uint32_t *tx_index = proof; - if (num_proof_bytes < sizeof *tx_index) return 1; - if (*tx_index >= MAX_TRANSACTIONS_PER_PAYLOAD) return 1; - info->tx_index = *tx_index; - consume(sizeof *tx_index); - - // tx_branch - const Root *tx_branch = proof; - if (num_proof_bytes < (1 + TX_DEPTH) * sizeof *tx_branch) return 1; - consume((1 + TX_DEPTH) * sizeof *tx_branch); - - // tx_selector - const uint8_t *tx_selector = proof; - if (num_proof_bytes < sizeof *tx_selector) return 1; - consume(sizeof *tx_selector); - - // tx_root - const uint8_t *y_parity; - const Bytes32 *r; - const Bytes32 *s; - switch (*tx_selector) { - case 3: { - // nonce - const uint64_t *nonce = proof; - if (num_proof_bytes < sizeof *nonce) return 1; - info->nonce = *nonce; - consume(sizeof *nonce); - - // max_priority_fee_per_gas - const Bytes32 *prio = proof; - if (num_proof_bytes < sizeof *prio) return 1; - memcpy(info->limits.max_priority_fee_per_gas, prio, sizeof *prio); - consume(sizeof *prio); - - // max_fee_per_gas - const Bytes32 *max_fee = proof; - if (num_proof_bytes < sizeof *max_fee) return 1; - memcpy(info->limits.max_fee_per_gas, max_fee, sizeof *max_fee); - consume(sizeof *max_fee); - - // gas - const uint64_t *gas = proof; - if (num_proof_bytes < sizeof *gas) return 1; - info->limits.gas = *gas; - consume(sizeof *gas); - - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 245) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - memcpy(info->tx_value, value, sizeof *value); - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // signature.y_parity - y_parity = proof; - if (num_proof_bytes < sizeof *y_parity) return 1; - if (*y_parity > 1) return 1; - consume(sizeof *y_parity); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - consume(sizeof *to_selector); - - // to - switch (*to_selector) { - case 0: { - info->tx_to.destination_type = DESTINATION_TYPE_CREATE; - - if (num_proof_bytes) return 1; - /* 21 */ memcpy(&scratch[0], &zero_hash[1], 32); - } break; - case 1: { - info->tx_to.destination_type = DESTINATION_TYPE_REGULAR; - - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - memcpy(info->tx_to.address, to, sizeof *to); - consume(sizeof *to); - - if (num_proof_bytes) return 1; - /* 42 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 43 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 21 */ hash_combine(&scratch[0], &root, &scratch[0]); - } break; - default: return 1; - } - - // sig_root - /* 20 */ memcpy(&root[0], gas, 8); memset(&root[8], 0, 24); - /* 10 */ hash_combine(&root, &root, &scratch[0]); - /* 11 */ hash_combine(&scratch[0], value, &multi_branch[0]); - /* 5 */ hash_combine(&scratch[0], &root, &scratch[0]); - /* 17 */ memcpy(&root[0], nonce, 8); memset(&root[8], 0, 24); - /* 8 */ hash_combine(&root, &cfg->chain_id, &root); - /* 9 */ hash_combine(&scratch[1], prio, max_fee); - /* 4 */ hash_combine(&root, &root, &scratch[1]); - /* 2 */ hash_combine(&root, &root, &scratch[0]); - /* 1 */ hash_combine(&root, &root, &multi_branch[1]); - - // signature_root - /* 4 */ scratch[0][0] = *y_parity; memset(&scratch[0][1], 0, 31); - /* 2 */ hash_combine(&scratch[0], &scratch[0], r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - case 2: { - // nonce - const uint64_t *nonce = proof; - if (num_proof_bytes < sizeof *nonce) return 1; - info->nonce = *nonce; - consume(sizeof *nonce); - - // max_priority_fee_per_gas - const Bytes32 *prio = proof; - if (num_proof_bytes < sizeof *prio) return 1; - memcpy(info->limits.max_priority_fee_per_gas, prio, sizeof *prio); - consume(sizeof *prio); - - // max_fee_per_gas - const Bytes32 *max_fee = proof; - if (num_proof_bytes < sizeof *max_fee) return 1; - memcpy(info->limits.max_fee_per_gas, max_fee, sizeof *max_fee); - consume(sizeof *max_fee); - - // gas_limit - const uint64_t *gas_limit = proof; - if (num_proof_bytes < sizeof *gas_limit) return 1; - info->limits.gas = *gas_limit; - consume(sizeof *gas_limit); - - // &destination - const uint32_t *destination_offset = proof; - if (num_proof_bytes < sizeof *destination_offset) return 1; - if (*destination_offset != 245) return 1; - consume(sizeof *destination_offset); - - // amount - const Bytes32 *amount = proof; - if (num_proof_bytes < sizeof *amount) return 1; - memcpy(info->tx_value, amount, sizeof *amount); - consume(sizeof *amount); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 2 * sizeof *multi_branch) return 1; - consume(2 * sizeof *multi_branch); - - // signature.y_parity - y_parity = proof; - if (num_proof_bytes < sizeof *y_parity) return 1; - if (*y_parity > 1) return 1; - consume(sizeof *y_parity); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // destination_selector - const uint8_t *destination_selector = proof; - if (num_proof_bytes < sizeof *destination_selector) return 1; - if (*destination_selector != 1) return 1; - consume(sizeof *destination_selector); - - // destination - switch (*destination_selector) { - case 0: { - info->tx_to.destination_type = DESTINATION_TYPE_CREATE; - - if (num_proof_bytes) return 1; - /* 21 */ memcpy(&scratch[0], &zero_hash[1], 32); - } break; - case 1: { - info->tx_to.destination_type = DESTINATION_TYPE_REGULAR; - - const ExecutionAddress *d = proof; - if (num_proof_bytes < sizeof *d) return 1; - memcpy(info->tx_to.address, d, sizeof *d); - consume(sizeof *d); - - if (num_proof_bytes) return 1; - /* 42 */ memcpy(&root[0], d, 20); memset(&root[20], 0, 12); - /* 43 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 21 */ hash_combine(&scratch[0], &root, &scratch[0]); - } break; - default: return 1; - } - - // sig_root - /* 20 */ memcpy(&root[0], gas_limit, 8); memset(&root[8], 0, 24); - /* 10 */ hash_combine(&root, &root, &scratch[0]); - /* 11 */ hash_combine(&scratch[0], amount, &multi_branch[0]); - /* 5 */ hash_combine(&scratch[0], &root, &scratch[0]); - /* 17 */ memcpy(&root[0], nonce, 8); memset(&root[8], 0, 24); - /* 8 */ hash_combine(&root, &cfg->chain_id, &root); - /* 9 */ hash_combine(&scratch[1], prio, max_fee); - /* 4 */ hash_combine(&root, &root, &scratch[1]); - /* 2 */ hash_combine(&root, &root, &scratch[0]); - /* 1 */ hash_combine(&root, &root, &multi_branch[1]); - - // signature_root - /* 4 */ scratch[0][0] = *y_parity; memset(&scratch[0][1], 0, 31); - /* 2 */ hash_combine(&scratch[0], &scratch[0], r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - case 1: { - // nonce - const uint64_t *nonce = proof; - if (num_proof_bytes < sizeof *nonce) return 1; - info->nonce = *nonce; - consume(sizeof *nonce); - - // gas_price - const Bytes32 *price = proof; - if (num_proof_bytes < sizeof *price) return 1; - memcpy(info->limits.max_priority_fee_per_gas, price, sizeof *price); - memcpy(info->limits.max_fee_per_gas, price, sizeof *price); - consume(sizeof *price); - - // gas - const uint64_t *gas = proof; - if (num_proof_bytes < sizeof *gas) return 1; - info->limits.gas = *gas; - consume(sizeof *gas); - - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 181) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - memcpy(info->tx_value, value, sizeof *value); - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 1 * sizeof *multi_branch) return 1; - consume(1 * sizeof *multi_branch); - - // signature.y_parity - y_parity = proof; - if (num_proof_bytes < sizeof *y_parity) return 1; - if (*y_parity > 1) return 1; - consume(sizeof *y_parity); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - switch (*to_selector) { - case 0: { - info->tx_to.destination_type = DESTINATION_TYPE_CREATE; - - if (num_proof_bytes) return 1; - /* 12 */ memcpy(&root, &zero_hash[1], 32); - } break; - case 1: { - info->tx_to.destination_type = DESTINATION_TYPE_REGULAR; - - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - memcpy(info->tx_to.address, to, sizeof *to); - consume(sizeof *to); - - if (num_proof_bytes) return 1; - /* 24 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 25 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 12 */ hash_combine(&root, &root, &scratch[0]); - } break; - default: return 1; - } - - // sig_root - /* 6 */ hash_combine(&scratch[1], &root, value); - /* 11 */ memcpy(&root[0], gas, 8); memset(&root[8], 0, 24); - /* 5 */ hash_combine(&scratch[0], price, &root); - /* 9 */ memcpy(&root[0], nonce, 8); memset(&root[8], 0, 24); - /* 4 */ hash_combine(&root, &cfg->chain_id, &root); - /* 2 */ hash_combine(&root, &root, &scratch[0]); - /* 3 */ hash_combine(&scratch[0], &scratch[1], &multi_branch[0]); - /* 1 */ hash_combine(&root, &root, &scratch[0]); - - // signature_root - /* 4 */ scratch[0][0] = *y_parity; memset(&scratch[0][1], 0, 31); - /* 2 */ hash_combine(&scratch[0], &scratch[0], r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - case 0: { - // nonce - const uint64_t *nonce = proof; - if (num_proof_bytes < sizeof *nonce) return 1; - info->nonce = *nonce; - consume(sizeof *nonce); - - // gasprice - const Bytes32 *price = proof; - if (num_proof_bytes < sizeof *price) return 1; - memcpy(info->limits.max_priority_fee_per_gas, price, sizeof *price); - memcpy(info->limits.max_fee_per_gas, price, sizeof *price); - consume(sizeof *price); - - // startgas - const uint64_t *startgas = proof; - if (num_proof_bytes < sizeof *startgas) return 1; - info->limits.gas = *startgas; - consume(sizeof *startgas); - - // &to - const uint32_t *to_offset = proof; - if (num_proof_bytes < sizeof *to_offset) return 1; - if (*to_offset != 212) return 1; - consume(sizeof *to_offset); - - // value - const Bytes32 *value = proof; - if (num_proof_bytes < sizeof *value) return 1; - memcpy(info->tx_value, value, sizeof *value); - consume(sizeof *value); - - // multi_branch - const Root *multi_branch = proof; - if (num_proof_bytes < 1 * sizeof *multi_branch) return 1; - consume(1 * sizeof *multi_branch); - - // signature.v - const Bytes32 *v = proof; - if (num_proof_bytes < sizeof *v) return 1; - y = (((*v)[0] & 0x1) == 0); - y_parity = &y; - consume(sizeof *v); - - // signature.r - r = proof; - if (num_proof_bytes < sizeof *r) return 1; - consume(sizeof *r); - - // signature.s - s = proof; - if (num_proof_bytes < sizeof *s) return 1; - consume(sizeof *s); - - // to_selector - const uint8_t *to_selector = proof; - if (num_proof_bytes < sizeof *to_selector) return 1; - if (*to_selector != 1) return 1; - consume(sizeof *to_selector); - - // to - switch (*to_selector) { - case 0: { - info->tx_to.destination_type = DESTINATION_TYPE_CREATE; - - if (num_proof_bytes) return 1; - /* 11 */ memcpy(&scratch[0], &zero_hash[1], 32); - } break; - case 1: { - info->tx_to.destination_type = DESTINATION_TYPE_REGULAR; - - const ExecutionAddress *to = proof; - if (num_proof_bytes < sizeof *to) return 1; - memcpy(info->tx_to.address, to, sizeof *to); - consume(sizeof *to); - - if (num_proof_bytes) return 1; - /* 22 */ memcpy(&root[0], to, 20); memset(&root[20], 0, 12); - /* 23 */ scratch[0][0] = 1; memset(&scratch[0][1], 0, 31); - /* 11 */ hash_combine(&scratch[0], &root, &scratch[0]); - } break; - default: return 1; - } - - // sig_root - /* 10 */ memcpy(&root[0], startgas, 8); memset(&root[8], 0, 24); - /* 5 */ hash_combine(&scratch[0], &root, &scratch[0]); - /* 6 */ hash_combine(&scratch[1], value, &multi_branch[0]); - /* 3 */ hash_combine(&scratch[1], &scratch[1], &zero_hash[1]); - /* 8 */ memcpy(&root[0], nonce, 8); memset(&root[8], 0, 24); - /* 4 */ hash_combine(&root, &root, price); - /* 2 */ hash_combine(&root, &root, &scratch[0]); - /* 1 */ hash_combine(&root, &root, &scratch[1]); - - // signature_root - /* 2 */ hash_combine(&scratch[0], v, r); - /* 3 */ hash_combine(&scratch[1], s, &zero_hash[0]); - /* 1 */ hash_combine(&scratch[0], &scratch[0], &scratch[1]); - } break; - default: return 1; - } - - // tx_from - uint8_t ser_sig[64]; - for (size_t i = 0; i < 32; i++) { - ser_sig[i] = (*r)[31 - i]; - ser_sig[i + 32] = (*s)[31 - i]; - } - secp256k1_ecdsa_recoverable_signature recover_sig; - if (!secp256k1_ecdsa_recoverable_signature_parse_compact( - secp256k1_context_static, &recover_sig, ser_sig, *y_parity)) - { - return 1; - } - secp256k1_pubkey public_key; - if (!secp256k1_ecdsa_recover( - secp256k1_context_static, &public_key, &recover_sig, &root[0])) - { - return 1; - } - uint8_t uncompressed[65]; - size_t n = sizeof uncompressed; - if (!secp256k1_ec_pubkey_serialize( - secp256k1_context_static, uncompressed, &n, - &public_key, SECP256K1_EC_UNCOMPRESSED)) - { - debug("secp256k1_ec_pubkey_serialize failed\n"); - } - if (n != sizeof uncompressed) - debug("secp256k1_ec_pubkey_serialize failed: Length %zu\n", n); - sha3_context ctx; - if (sha3_Init(&ctx, 256)) - debug("sha3_Init failed\n"); - if (sha3_SetFlags(&ctx, SHA3_FLAGS_KECCAK) != SHA3_FLAGS_KECCAK) - debug("sha3_SetFlags failed\n"); - sha3_Update(&ctx, uncompressed, sizeof uncompressed); - memcpy(info->tx_from, &((const uint8_t *) sha3_Finalize(&ctx))[12], 20); - - // tx_to.address - switch (info->tx_to.destination_type) { - case DESTINATION_TYPE_REGULAR: break; - case DESTINATION_TYPE_CREATE: { - ExecutionAddress *address = &info->tx_to.address; - uint8_t n = 0; - uint8_t rlp_data[30]; - rlp_data[0] = 0x80 + 20; - memcpy(&rlp_data[1], info->tx_from, 20); - if (info->nonce <= 0x7f) - rlp_data[21] = (uint8_t) info->nonce; - else { - for (int i = 64 - 8; i >= 0; i -= 8) { - uint8_t b = (uint8_t) (info->nonce >> i); - if (b || n) - rlp_data[22 + n++] = b; - } - rlp_data[21] = 0x80 + n; - } - n += 22; - if (sha3_Init(&ctx, 256)) - debug("sha3_Init failed\n"); - if (sha3_SetFlags(&ctx, SHA3_FLAGS_KECCAK) != SHA3_FLAGS_KECCAK) - debug("sha3_SetFlags failed\n"); - sha3_Update(&ctx, rlp_data, n); - memcpy(address, &((const uint8_t *) sha3_Finalize(&ctx))[12], 20); - } break; - } - - // tx_root - hash_combine(&info->tx_hash, &root, &scratch[0]); - - // transaction_root - /* 3 */ scratch[0][0] = *tx_selector; memset(&scratch[0][1], 0, 31); - /* 1 */ hash_combine(&root, &info->tx_hash, &scratch[0]); - - // transactions_root - for (int i = 0; i < TX_DEPTH; i++) { - if (*tx_index & ((uint32_t) 1 << i)) - hash_combine(&root, tx_branch, &root); - else - hash_combine(&root, &root, tx_branch); - tx_branch++; - } - hash_combine(&root, &root, tx_branch); - if (memcmp(&root, transactions_root, sizeof root)) return 1; - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// - -#define STR(x) #x -#define QUOTE(x) STR(x) -__asm__ ( - ".section .rodata\n" - ".global transactions_root\n" - "transactions_root:\n" - ".incbin \"proofs/transactions_root.ssz\"\n" - ".global proof\n" - "proof:\n" - ".incbin \"proofs/" QUOTE(PROOF_TYPE) "_" QUOTE(PROOF_INDEX) ".ssz\"\n" - ".global num_proof_bytes\n" - ".set num_proof_bytes, . - proof\n" - ".section .text\n" -); -extern const uint8_t transactions_root[]; -extern const uint8_t proof[]; -extern const uint8_t num_proof_bytes[]; - -const ExecutionConfig cfg = { - .chain_id = { - 0x39, 0x05 - } -}; - -typedef enum { - NIL, - TRANSACTION, - AMOUNT, - SENDER, - INFO -} proof_type; - -void main(void) -{ - k_msleep(1000); - - printk("Union %s_%s (%zu bytes)\n", - QUOTE(PROOF_TYPE), QUOTE(PROOF_INDEX), (size_t) num_proof_bytes); - - timing_init(); - timing_start(); - timing_t start_time, end_time; - switch (PROOF_TYPE) { - case NIL: { - } break; - case TRANSACTION: { - const Root *expected_tx_hash = (const Root *) &proof[0]; - start_time = timing_counter_get(); - if (verify_transaction_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - expected_tx_hash)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("tx_index = %u\n", proof[33]); - } break; - case AMOUNT: { - const ExecutionAddress expected_tx_to = { - 0xd8, 0xda, 0x6b, 0xf2, 0x69, 0x64, 0xaf, 0x9d, 0x7e, 0xed, - 0x9e, 0x03, 0xe5, 0x34, 0x15, 0xd3, 0x7a, 0xa9, 0x60, 0x45, - }; - const Bytes32 expected_tx_value_min = { - 0x00, 0xca, 0x9a, 0x3b - }; - start_time = timing_counter_get(); - if (verify_amount_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - &expected_tx_to, &expected_tx_value_min)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("OK\n"); - } break; - case SENDER: { - const ExecutionAddress expected_tx_to = { - 0xd8, 0xda, 0x6b, 0xf2, 0x69, 0x64, 0xaf, 0x9d, 0x7e, 0xed, - 0x9e, 0x03, 0xe5, 0x34, 0x15, 0xd3, 0x7a, 0xa9, 0x60, 0x45, - }; - const Bytes32 expected_tx_value_min = { - 0x00, 0xca, 0x9a, 0x3b - }; - ExecutionAddress tx_from; - start_time = timing_counter_get(); - if (verify_sender_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - &expected_tx_to, &expected_tx_value_min, - &tx_from)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("tx_from = 0x"); - for (size_t i = 0; i < sizeof tx_from; i++) - printk("%02x", tx_from[i]); - printk("\n"); - } break; - case INFO: { - TransactionInfo info; - start_time = timing_counter_get(); - if (verify_info_proof( - proof, (size_t) num_proof_bytes, - &cfg, (const Root *) transactions_root, - &info)) - { - printk("ERROR\n"); - break; - } - end_time = timing_counter_get(); - printk("info = {\n"); - printk(" tx_index = %lu\n", (unsigned long) info.tx_index); - printk(" tx_hash = 0x"); - for (size_t i = 0; i < sizeof info.tx_hash; i++) - printk("%02x", info.tx_hash[i]); - printk("\n"); - printk(" tx_from = 0x"); - for (size_t i = 0; i < sizeof info.tx_from; i++) - printk("%02x", info.tx_from[i]); - printk("\n"); - printk(" nonce = %llu\n", (unsigned long long) info.nonce); - printk(" tx_to = {\n"); - printk(" destination_type = %u\n", info.tx_to.destination_type); - printk(" address = 0x"); - for (size_t i = 0; i < sizeof info.tx_to.address; i++) - printk("%02x", info.tx_to.address[i]); - printk("\n"); - printk(" }\n"); - printk(" tx_value = 0x"); - for (int i = sizeof info.tx_value - 1; i >= 0; i--) - printk("%02x", info.tx_value[i]); - printk("\n"); - printk(" limits = {\n"); - printk(" max_priority_fee_per_gas = 0x"); - for (int i = sizeof (Bytes32) - 1; i >= 0; i--) - printk("%02x", info.limits.max_priority_fee_per_gas[i]); - printk("\n"); - printk(" max_fee_per_gas = 0x"); - for (int i = sizeof (Bytes32) - 1; i >= 0; i--) - printk("%02x", info.limits.max_fee_per_gas[i]); - printk("\n"); - printk(" gas = %llu\n", (unsigned long long) info.limits.gas); - printk(" }\n"); - printk("}\n"); - } break; - } - uint64_t total_cycles = timing_cycles_get(&start_time, &end_time); - uint64_t total_ns = timing_cycles_to_ns(total_cycles); - printk("cycles = %llu (%llu.%06llu ms)\n", - (unsigned long long) total_cycles, - (unsigned long long) (total_ns / 1000000), - (unsigned long long) (total_ns % 1000000)); - timing_stop(); -} diff --git a/assets/eip-6404/tests/union/verify_proofs.py b/assets/eip-6404/tests/union/verify_proofs.py deleted file mode 100644 index 2822b3b..0000000 --- a/assets/eip-6404/tests/union/verify_proofs.py +++ /dev/null @@ -1,417 +0,0 @@ -from remerkleable.settings import zero_hashes -from secp256k1 import ECDSA, PublicKey -from create_proofs import * - -def verify_transaction_proof( - proof: TransactionProof, - cfg: ExecutionConfig, - transactions_root: Root, - expected_tx_hash: Root, -): - assert proof.tx_root == expected_tx_hash - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - proof.tx_root.hash_tree_root(), - proof.tx_selector.hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 2 + 0, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - -def verify_amount_proof( - proof: AmountProof, - cfg: ExecutionConfig, - transactions_root: Root, - expected_tx_to: ExecutionAddress, - expected_tx_value_min: uint256, -): - match proof.tx_proof.selector(): - case 3: - assert proof.tx_proof.value().to.selector() == 1 - assert proof.tx_proof.value().to.value() == expected_tx_to - assert proof.tx_proof.value().value >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().gas.hash_tree_root(), - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP4844_AMOUNT_PROOF_INDICES, - EIP4844_AMOUNT_PROOF_HELPER_INDICES, - ) - case 2: - assert proof.tx_proof.value().destination.selector() == 1 - assert proof.tx_proof.value().destination.value() == expected_tx_to - assert proof.tx_proof.value().amount >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().gas_limit.hash_tree_root(), - proof.tx_proof.value().destination.hash_tree_root(), - proof.tx_proof.value().amount.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP1559_AMOUNT_PROOF_INDICES, - EIP1559_AMOUNT_PROOF_HELPER_INDICES, - ) - case 1: - assert proof.tx_proof.value().to.selector() == 1 - assert proof.tx_proof.value().to.value() == expected_tx_to - assert proof.tx_proof.value().value >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP2930_AMOUNT_PROOF_INDICES, - EIP2930_AMOUNT_PROOF_HELPER_INDICES, - ) - case 0: - assert proof.tx_proof.value().to.selector() == 1 - assert proof.tx_proof.value().to.value() == expected_tx_to - assert proof.tx_proof.value().value >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().startgas.hash_tree_root(), - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - zero_hashes[1], - ], - proof.tx_proof.value().multi_branch, - LEGACY_AMOUNT_PROOF_INDICES, - LEGACY_AMOUNT_PROOF_HELPER_INDICES, - ) - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - sig_root, - proof.tx_proof.value().signature_root.hash_tree_root(), - uint8(proof.tx_proof.selector()).hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 4 + 0, - tx_gindex * 4 + 1, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - -def verify_sender_proof( - proof: SenderProof, - cfg: ExecutionConfig, - transactions_root: Root, - expected_tx_to: ExecutionAddress, - expected_tx_value_min: uint256, -) -> ExecutionAddress: - match proof.tx_proof.selector(): - case 3: - assert proof.tx_proof.value().to.selector() == 1 - assert proof.tx_proof.value().to.value() == expected_tx_to - assert proof.tx_proof.value().value >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().gas.hash_tree_root(), - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP4844_SENDER_PROOF_INDICES, - EIP4844_SENDER_PROOF_HELPER_INDICES, - ) - signature = proof.tx_proof.value().signature - case 2: - assert proof.tx_proof.value().destination.selector() == 1 - assert proof.tx_proof.value().destination.value() == expected_tx_to - assert proof.tx_proof.value().amount >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().gas_limit.hash_tree_root(), - proof.tx_proof.value().destination.hash_tree_root(), - proof.tx_proof.value().amount.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP1559_SENDER_PROOF_INDICES, - EIP1559_SENDER_PROOF_HELPER_INDICES, - ) - signature = proof.tx_proof.value().signature - case 1: - assert proof.tx_proof.value().to.selector() == 1 - assert proof.tx_proof.value().to.value() == expected_tx_to - assert proof.tx_proof.value().value >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP2930_SENDER_PROOF_INDICES, - EIP2930_SENDER_PROOF_HELPER_INDICES, - ) - signature = proof.tx_proof.value().signature - case 0: - assert proof.tx_proof.value().to.selector() == 1 - assert proof.tx_proof.value().to.value() == expected_tx_to - assert proof.tx_proof.value().value >= expected_tx_value_min - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().startgas.hash_tree_root(), - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - zero_hashes[1], - ], - proof.tx_proof.value().multi_branch, - LEGACY_SENDER_PROOF_INDICES, - LEGACY_SENDER_PROOF_HELPER_INDICES, - ) - signature = ECDSASignature( - y_parity=(proof.tx_proof.value().signature.v & 0x1) == 0, - r = proof.tx_proof.value().signature.r, - s = proof.tx_proof.value().signature.s, - ) - - ecdsa = ECDSA() - recover_sig = ecdsa.ecdsa_recoverable_deserialize( - signature.r.to_bytes(32, 'big') + signature.s.to_bytes(32, 'big'), - 0x01 if signature.y_parity else 0, - ) - public_key = PublicKey(ecdsa.ecdsa_recover(sig_root, recover_sig, raw=True)) - uncompressed = public_key.serialize(compressed=False) - tx_from = ExecutionAddress(keccak(uncompressed)[12:32]) - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - sig_root, - proof.tx_proof.value().signature.hash_tree_root(), - uint8(proof.tx_proof.selector()).hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 4 + 0, - tx_gindex * 4 + 1, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - - return tx_from - -def verify_info_proof( - proof: InfoProof, - cfg: ExecutionConfig, - transactions_root: Root, -) -> TransactionInfo: - match proof.tx_proof.selector(): - case 3: - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().nonce.hash_tree_root(), - proof.tx_proof.value().max_priority_fee_per_gas.hash_tree_root(), - proof.tx_proof.value().max_fee_per_gas.hash_tree_root(), - proof.tx_proof.value().gas.hash_tree_root(), - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - cfg.chain_id.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP4844_INFO_PROOF_INDICES, - EIP4844_INFO_PROOF_HELPER_INDICES, - ) - print(sig_root.hex()) - to = proof.tx_proof.value().to - signature = proof.tx_proof.value().signature - info = TransactionInfo( - tx_index=proof.tx_index, - nonce=proof.tx_proof.value().nonce, - tx_value=proof.tx_proof.value().value, - limits=TransactionLimits( - max_priority_fee_per_gas=proof.tx_proof.value().max_priority_fee_per_gas, - max_fee_per_gas=proof.tx_proof.value().max_fee_per_gas, - gas=proof.tx_proof.value().gas, - ), - ) - case 2: - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().nonce.hash_tree_root(), - proof.tx_proof.value().max_priority_fee_per_gas.hash_tree_root(), - proof.tx_proof.value().max_fee_per_gas.hash_tree_root(), - proof.tx_proof.value().gas_limit.hash_tree_root(), - proof.tx_proof.value().destination.hash_tree_root(), - proof.tx_proof.value().amount.hash_tree_root(), - cfg.chain_id.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP1559_INFO_PROOF_INDICES, - EIP1559_INFO_PROOF_HELPER_INDICES, - ) - to = proof.tx_proof.value().destination - signature = proof.tx_proof.value().signature - info = TransactionInfo( - tx_index=proof.tx_index, - nonce=proof.tx_proof.value().nonce, - tx_value=proof.tx_proof.value().amount, - limits=TransactionLimits( - max_priority_fee_per_gas=proof.tx_proof.value().max_priority_fee_per_gas, - max_fee_per_gas=proof.tx_proof.value().max_fee_per_gas, - gas=proof.tx_proof.value().gas_limit, - ), - ) - case 1: - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().nonce.hash_tree_root(), - proof.tx_proof.value().gas_price.hash_tree_root(), - proof.tx_proof.value().gas_limit.hash_tree_root(), - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - cfg.chain_id.hash_tree_root(), - ], - proof.tx_proof.value().multi_branch, - EIP2930_INFO_PROOF_INDICES, - EIP2930_INFO_PROOF_HELPER_INDICES, - ) - to = proof.tx_proof.value().to - signature = proof.tx_proof.value().signature - info = TransactionInfo( - tx_index=proof.tx_index, - nonce=proof.tx_proof.value().nonce, - tx_value=proof.tx_proof.value().value, - limits=TransactionLimits( - max_priority_fee_per_gas=proof.tx_proof.value().gas_price, - max_fee_per_gas=proof.tx_proof.value().gas_price, - gas=proof.tx_proof.value().gas_limit, - ), - ) - case 0: - sig_root = calculate_multi_merkle_root( - [ - proof.tx_proof.value().nonce.hash_tree_root(), - proof.tx_proof.value().gasprice.hash_tree_root(), - proof.tx_proof.value().startgas.hash_tree_root(), - proof.tx_proof.value().to.hash_tree_root(), - proof.tx_proof.value().value.hash_tree_root(), - zero_hashes[1], - ], - proof.tx_proof.value().multi_branch, - LEGACY_INFO_PROOF_INDICES, - LEGACY_INFO_PROOF_HELPER_INDICES, - ) - to = proof.tx_proof.value().to - signature = ECDSASignature( - y_parity=(proof.tx_proof.value().signature.v & 0x1) == 0, - r = proof.tx_proof.value().signature.r, - s = proof.tx_proof.value().signature.s, - ) - info = TransactionInfo( - tx_index=proof.tx_index, - nonce=proof.tx_proof.value().nonce, - tx_value=proof.tx_proof.value().value, - limits=TransactionLimits( - max_priority_fee_per_gas=proof.tx_proof.value().gasprice, - max_fee_per_gas=proof.tx_proof.value().gasprice, - gas=proof.tx_proof.value().startgas, - ), - ) - - ecdsa = ECDSA() - recover_sig = ecdsa.ecdsa_recoverable_deserialize( - signature.r.to_bytes(32, 'big') + signature.s.to_bytes(32, 'big'), - 0x01 if signature.y_parity else 0, - ) - public_key = PublicKey(ecdsa.ecdsa_recover(sig_root, recover_sig, raw=True)) - uncompressed = public_key.serialize(compressed=False) - info.tx_from = ExecutionAddress(keccak(uncompressed)[12:32]) - - match to.selector(): - case 1: - info.tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_REGULAR, - address=to.value(), - ) - case 0: - info.tx_to = DestinationAddress( - destination_type=DESTINATION_TYPE_CREATE, - address=compute_contract_address(info.tx_from, info.nonce), - ) - - info.tx_hash = calculate_multi_merkle_root( - [ - sig_root, - proof.tx_proof.value().signature.hash_tree_root(), - ], - [], - [ - GeneralizedIndex(2), - GeneralizedIndex(3), - ], - [], - ) - - tx_gindex = GeneralizedIndex(MAX_TRANSACTIONS_PER_PAYLOAD * 2 + uint64(proof.tx_index)) - assert calculate_multi_merkle_root( - [ - info.tx_hash.hash_tree_root(), - uint8(proof.tx_proof.selector()).hash_tree_root(), - ], - proof.tx_branch, - [ - tx_gindex * 2 + 0, - tx_gindex * 2 + 1, - ], - get_helper_indices([tx_gindex]), - ) == transactions_root - - return info - -if __name__ == '__main__': - print('transactions_root') - print(f'0x{transactions_root.hex()}') - - print() - for tx_index in range(len(transaction_proofs)): - print(f'{tx_index} - TransactionProof') - expected_tx_hash = transaction_proofs[tx_index].tx_root - verify_transaction_proof( - transaction_proofs[tx_index], cfg, transactions_root, - expected_tx_hash) - print(f'tx_index = {transaction_proofs[tx_index].tx_index}') - - print() - for tx_index in range(len(amount_proofs)): - print(f'{tx_index} - AmountProof') - expected_tx_to = ExecutionAddress(bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045')) - expected_tx_value_min = 1_000_000_000 - verify_amount_proof( - amount_proofs[tx_index], cfg, transactions_root, - expected_tx_to, expected_tx_value_min, - ) - print(f'OK') - - print() - for tx_index in range(len(sender_proofs)): - print(f'{tx_index} - SenderProof') - expected_tx_to = ExecutionAddress(bytes.fromhex('d8dA6BF26964aF9D7eEd9e03E53415D37aA96045')) - expected_tx_value_min = 1_000_000_000 - tx_from = verify_sender_proof( - sender_proofs[tx_index], cfg, transactions_root, - expected_tx_to, expected_tx_value_min, - ) - print(f'tx_from = 0x{tx_from.hex()}') - - print() - for tx_index in range(len(info_proofs)): - print(f'{tx_index} - InfoProof') - info = verify_info_proof( - info_proofs[tx_index], cfg, transactions_root, - ) - print(f'info = {info}') diff --git a/assets/eip-6454/contracts/INonTransferable.sol b/assets/eip-6454/contracts/INonTransferable.sol deleted file mode 100644 index cfef7d6..0000000 --- a/assets/eip-6454/contracts/INonTransferable.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -interface INonTransferable { - - /** - * @notice Used to check whether the given token is nonTransferable or not. - * @param tokenId ID of the token being checked - * @return Boolean value indicating whether the given token is nonTransferable - */ - function isNonTransferable(uint256 tokenId) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-6454/contracts/mocks/ERC721NonTransferableMock.sol b/assets/eip-6454/contracts/mocks/ERC721NonTransferableMock.sol deleted file mode 100644 index 5584aa6..0000000 --- a/assets/eip-6454/contracts/mocks/ERC721NonTransferableMock.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "../INonTransferable.sol"; -import "hardhat/console.sol"; - -error CannotTransferNonTransferable(); - -/** - * @title ERC721NonTransferableMock - * Used for tests - */ -contract ERC721NonTransferableMock is INonTransferable, ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} - - function mint(address to, uint256 amount) public { - _mint(to, amount); - } - - function burn(uint256 tokenId) public { - _burn(tokenId); - } - - function isNonTransferable(uint256 tokenId) public view returns (bool) { - _requireMinted(tokenId); - return true; - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 firstTokenId, - uint256 batchSize - ) internal virtual override { - super._beforeTokenTransfer(from, to, firstTokenId, batchSize); - - // exclude minting and burning - if ( from != address(0) && to != address(0)) { - uint256 lastTokenId = firstTokenId + batchSize; - for (uint256 i = firstTokenId; i < lastTokenId; i++) { - uint256 tokenId = firstTokenId + i; - if (isNonTransferable(tokenId)) { - revert CannotTransferNonTransferable(); - } - unchecked { - i++; - } - } - } - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC721) returns (bool) { - return interfaceId == type(INonTransferable).interfaceId - || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-6454/hardhat.config.ts b/assets/eip-6454/hardhat.config.ts deleted file mode 100644 index f1b6fde..0000000 --- a/assets/eip-6454/hardhat.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.16", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6454/package.json b/assets/eip-6454/package.json deleted file mode 100644 index e53470a..0000000 --- a/assets/eip-6454/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "non-transferable-tokens", - "scripts": { - "test": "yarn typechain && hardhat test", - "typechain": "hardhat typechain", - "prettier": "prettier --write ." - }, - "engines": { - "node": ">=16.0.0" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4" - } -} diff --git a/assets/eip-6454/test/nonTransferable.ts b/assets/eip-6454/test/nonTransferable.ts deleted file mode 100644 index 94af4d4..0000000 --- a/assets/eip-6454/test/nonTransferable.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ERC721NonTransferableMock } from "../typechain-types"; - -async function nonTransferableTokenFixture(): Promise { - const factory = await ethers.getContractFactory("ERC721NonTransferableMock"); - const token = await factory.deploy("Chunky", "CHNK"); - await token.deployed(); - - return token; -} - -describe("NonTransferable", async function () { - let nonTransferable: ERC721NonTransferableMock; - let owner: SignerWithAddress; - let otherOwner: SignerWithAddress; - const tokenId = 1; - - beforeEach(async function () { - const signers = await ethers.getSigners(); - owner = signers[0]; - otherOwner = signers[1]; - nonTransferable = await loadFixture(nonTransferableTokenFixture); - - await nonTransferable.mint(owner.address, 1); - }); - - it("can support IRMRKNonTransferable", async function () { - expect(await nonTransferable.supportsInterface("0xa7331ab1")).to.equal(true); - }); - - it("does not support other interfaces", async function () { - expect(await nonTransferable.supportsInterface("0xffffffff")).to.equal(false); - }); - - it("cannot transfer", async function () { - expect( - nonTransferable - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - otherOwner.address, - tokenId - ) - ).to.be.revertedWithCustomError(nonTransferable, "CannotTransferNonTransferable"); - }); - - it("reverts if token does not exist", async function () { - expect( - nonTransferable - .isNonTransferable(10) - ).to.be.revertedWith("CannotTransferNonTransferable"); - }); - - it("can burn", async function () { - await nonTransferable.connect(owner).burn(tokenId); - await expect(nonTransferable.ownerOf(tokenId)).to.be.revertedWith( - "ERC721: invalid token ID" - ); - }); -}); diff --git a/assets/eip-6464/contracts/IERC165.sol b/assets/eip-6464/contracts/IERC165.sol deleted file mode 100644 index 9f87593..0000000 --- a/assets/eip-6464/contracts/IERC165.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} diff --git a/assets/eip-6464/contracts/IERC6464.sol b/assets/eip-6464/contracts/IERC6464.sol deleted file mode 100644 index da0a125..0000000 --- a/assets/eip-6464/contracts/IERC6464.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC721.sol"; - -/** - * @notice Extends ERC-721 to include per-token approval for multiple operators. - * @dev Off-chain indexers of approvals SHOULD assume that an operator is approved if either of `ERC721.Approval(…)` or - * `ERC721.ApprovalForAll(…, true)` events are witnessed without the corresponding revocation(s), even if an - * `ExplicitApprovalFor(…, false)` is emitted. - * @dev TODO: the ERC-165 identifier for this interface is TBD. - */ -interface IERC6464 is ERC721 { - /** - * @notice Emitted when approval is explicitly granted or revoked for a token. - */ - event ExplicitApprovalFor( - address indexed operator, - uint256 indexed tokenId, - bool approved - ); - - /** - * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are - * revoked for all tokens. - * @dev MUST be emitted upon calls to `revokeAllExplicitApprovals()`. - */ - event AllExplicitApprovalsRevoked(address indexed owner); - - /** - * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are - * revoked for the specific token. - * @param owner MUST be `ownerOf(tokenId)` as per ERC721; in the case of revocation due to transfer, this MUST be - * the `from` address expected to be emitted in the respective `ERC721.Transfer()` event. - */ - event AllExplicitApprovalsRevoked( - address indexed owner, - uint256 indexed tokenId - ); - - /** - * @notice Approves the operator to manage the asset on behalf of its owner. - * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner. - * @dev Approvals set via this method MUST be revoked upon transfer of the token to a new owner; equivalent to - * calling `revokeAllExplicitApprovals(tokenId)`, including associated events. - * @dev MUST emit `ApprovalFor(operator, tokenId, approved)`. - * @dev MUST NOT have an effect on any standard ERC721 approval setters / getters. - */ - function setExplicitApproval( - address operator, - uint256 tokenId, - bool approved - ) external; - - /** - * @notice Approves the operator to manage the token(s) on behalf of their owner. - * @dev MUST be equivalent to calling `setExplicitApprovalFor(operator, tokenId, approved)` for each `tokenId` in - * the array. - */ - function setExplicitApproval( - address operator, - uint256[] memory tokenIds, - bool approved - ) external; - - /** - * @notice Revokes all explicit approvals granted by `msg.sender`. - * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender)`. - */ - function revokeAllExplicitApprovals() external; - - /** - * @notice Revokes all excplicit approvals granted for the specified token. - * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner. - * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender, tokenId)`. - */ - function revokeAllExplicitApprovals(uint256 tokenId) external; - - /** - * @notice Query whether an address is an approved operator for a token. - */ - function isExplicitlyApprovedFor(address operator, uint256 tokenId) - external - view - returns (bool); -} - -interface IERC6464AnyApproval is ERC721 { - /** - * @notice Returns true if any of the following criteria are met: - * 1. `isExplicitlyApprovedFor(operator, tokenId) == true`; OR - * 2. `isApprovedForAll(ownerOf(tokenId), operator) == true`; OR - * 3. `getApproved(tokenId) == operator`. - * @dev The criteria MUST be extended if other mechanism(s) for approving operators are introduced. The criteria - * MUST include all approval approaches. - */ - function isApprovedFor(address operator, uint256 tokenId) - external - view - returns (bool); -} diff --git a/assets/eip-6464/contracts/IERC721.sol b/assets/eip-6464/contracts/IERC721.sol deleted file mode 100644 index cca0419..0000000 --- a/assets/eip-6464/contracts/IERC721.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC165.sol"; - -/// @title ERC-721 Non-Fungible Token Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-721 -/// Note: the ERC-165 identifier for this interface is 0x80ac58cd. -interface ERC721 is ERC165 { - /// @dev This emits when ownership of any NFT changes by any mechanism. - /// This event emits when NFTs are created (`from` == 0) and destroyed - /// (`to` == 0). Exception: during contract creation, any number of NFTs - /// may be created and assigned without emitting Transfer. At the time of - /// any transfer, the approved address for that NFT (if any) is reset to none. - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - /// @dev This emits when the approved address for an NFT is changed or - /// reaffirmed. The zero address indicates there is no approved address. - /// When a Transfer event emits, this also indicates that the approved - /// address for that NFT (if any) is reset to none. - event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); - - /// @dev This emits when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - - /// @notice Count all NFTs assigned to an owner - /// @dev NFTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param _owner An address for whom to query the balance - /// @return The number of NFTs owned by `_owner`, possibly zero - function balanceOf(address _owner) external view returns (uint256); - - /// @notice Find the owner of an NFT - /// @dev NFTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param _tokenId The identifier for an NFT - /// @return The address of the owner of the NFT - function ownerOf(uint256 _tokenId) external view returns (address); - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. When transfer is complete, this function - /// checks if `_to` is a smart contract (code size > 0). If so, it calls - /// `onERC721Received` on `_to` and throws if the return value is not - /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - /// @param data Additional data with no specified format, sent in call to `_to` - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external payable; - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev This works identically to the other function with an extra data parameter, - /// except this function just sets data to "". - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE - /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE - /// THEY MAY BE PERMANENTLY LOST - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Change or reaffirm the approved address for an NFT - /// @dev The zero address indicates there is no approved address. - /// Throws unless `msg.sender` is the current NFT owner, or an authorized - /// operator of the current owner. - /// @param _approved The new approved NFT controller - /// @param _tokenId The NFT to approve - function approve(address _approved, uint256 _tokenId) external payable; - - /// @notice Enable or disable approval for a third party ("operator") to manage - /// all of `msg.sender`'s assets - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// multiple operators per owner. - /// @param _operator Address to add to the set of authorized operators - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAll(address _operator, bool _approved) external; - - /// @notice Get the approved address for a single NFT - /// @dev Throws if `_tokenId` is not a valid NFT. - /// @param _tokenId The NFT to find the approved address for - /// @return The approved address for this NFT, or the zero address if there is none - function getApproved(uint256 _tokenId) external view returns (address); - - /// @notice Query if an address is an authorized operator for another address - /// @param _owner The address that owns the NFTs - /// @param _operator The address that acts on behalf of the owner - /// @return True if `_operator` is an approved operator for `_owner`, false otherwise - function isApprovedForAll(address _owner, address _operator) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-6465/helpers/convert_withdrawals.py b/assets/eip-6465/helpers/convert_withdrawals.py deleted file mode 100644 index 627809f..0000000 --- a/assets/eip-6465/helpers/convert_withdrawals.py +++ /dev/null @@ -1,13 +0,0 @@ -from rlp import decode -from ssz_withdrawal_types import * -from eip4895_withdrawal_types import * - -def normalize_withdrawal(encoded_withdrawal: bytes) -> Withdrawal: - withdrawal = decode(encoded_withdrawal, EIP4895Withdrawal) - - return Withdrawal( - index=withdrawal.index, - validator_index=withdrawal.validator_index, - address=withdrawal.address, - amount=withdrawal.amount, - ) diff --git a/assets/eip-6465/helpers/eip4895_withdrawal_types.py b/assets/eip-6465/helpers/eip4895_withdrawal_types.py deleted file mode 100644 index 3850c4d..0000000 --- a/assets/eip-6465/helpers/eip4895_withdrawal_types.py +++ /dev/null @@ -1,10 +0,0 @@ -from rlp import Serializable -from rlp.sedes import Binary, CountableList, List as RLPList, big_endian_int, binary - -class EIP4895Withdrawal(Serializable): - fields = ( - ('index', big_endian_int), - ('validator_index', big_endian_int), - ('address', Binary(20, 20)), - ('amount', big_endian_int), - ) diff --git a/assets/eip-6465/helpers/recover_withdrawals.py b/assets/eip-6465/helpers/recover_withdrawals.py deleted file mode 100644 index fe589c7..0000000 --- a/assets/eip-6465/helpers/recover_withdrawals.py +++ /dev/null @@ -1,14 +0,0 @@ -from rlp import encode -from ssz_withdrawal_types import * -from eip4895_withdrawal_types import * - -def recover_eip4895_withdrawal(withdrawal: Withdrawal) -> EIP4895Withdrawal: - return EIP4895Withdrawal( - index=withdrawal.index, - validator_index=withdrawal.validator_index, - address=withdrawal.address, - amount=withdrawal.amount, - ) - -def recover_encoded_withdrawal(withdrawal: Withdrawal) -> bytes: - return encode(recover_eip4895_withdrawal(withdrawal)) diff --git a/assets/eip-6465/helpers/ssz_withdrawal_types.py b/assets/eip-6465/helpers/ssz_withdrawal_types.py deleted file mode 100644 index a0ad7dd..0000000 --- a/assets/eip-6465/helpers/ssz_withdrawal_types.py +++ /dev/null @@ -1,21 +0,0 @@ -from remerkleable.basic import uint64 -from remerkleable.byte_arrays import ByteVector -from remerkleable.complex import Container - -class ValidatorIndex(uint64): - pass - -class Gwei(uint64): - pass - -class ExecutionAddress(ByteVector[20]): - pass - -class WithdrawalIndex(uint64): - pass - -class Withdrawal(Container): - index: WithdrawalIndex - validator_index: ValidatorIndex - address: ExecutionAddress - amount: Gwei diff --git a/assets/eip-6466/helpers/convert_receipts.py b/assets/eip-6466/helpers/convert_receipts.py deleted file mode 100644 index 0cdde5a..0000000 --- a/assets/eip-6466/helpers/convert_receipts.py +++ /dev/null @@ -1,69 +0,0 @@ -from rlp import decode -from ssz_receipt_types import * -from eip2718_receipt_types import * - -def normalize_receipt(encoded_receipt: bytes) -> Receipt: - eip2718_type = encoded_receipt[0] - - if eip2718_type == 0x05: # EIP-4844 - receipt = decode(encoded_receipt[1:], EIP4844Receipt) - - return Receipt( - status=receipt.status, - cumulative_gas_used=receipt.cumulative_gas_used, - logs_bloom=receipt.logs_bloom, - logs=[ReceiptLog( - address=log[0], - topics=log[1], - data=log[2] - ) for log in receipt.logs], - tx_type=TRANSACTION_TYPE_EIP4844, - cumulative_data_gas_used=Optional[uint64](receipt.cumulative_data_gas_used), - ) - - if eip2718_type == 0x02: # EIP-1559 - receipt = decode(encoded_receipt[1:], EIP1559Receipt) - - return Receipt( - status=receipt.status, - cumulative_gas_used=receipt.cumulative_transaction_gas_used, - logs_bloom=receipt.logs_bloom, - logs=[ReceiptLog( - address=log[0], - topics=log[1], - data=log[2] - ) for log in receipt.logs], - tx_type=TRANSACTION_TYPE_EIP1559, - ) - - if eip2718_type == 0x01: # EIP-2930 - receipt = decode(encoded_receipt[1:], EIP2930Receipt) - - return Receipt( - status=receipt.status, - cumulative_gas_used=receipt.cumulativeGasUsed, - logs_bloom=receipt.logsBloom, - logs=[ReceiptLog( - address=log[0], - topics=log[1], - data=log[2] - ) for log in receipt.logs], - tx_type=TRANSACTION_TYPE_EIP2930, - ) - - if 0xc0 <= eip2718_type <= 0xfe: # Legacy - receipt = decode(encoded_receipt, LegacyReceipt) - - return Receipt( - status=receipt.status, - cumulative_gas_used=receipt.cumulativeGasUsed, - logs_bloom=receipt.logsBloom, - logs=[ReceiptLog( - address=log[0], - topics=log[1], - data=log[2] - ) for log in receipt.logs], - tx_type=TRANSACTION_TYPE_LEGACY, - ) - - assert False diff --git a/assets/eip-6466/helpers/eip2718_receipt_types.py b/assets/eip-6466/helpers/eip2718_receipt_types.py deleted file mode 100644 index f9f88ad..0000000 --- a/assets/eip-6466/helpers/eip2718_receipt_types.py +++ /dev/null @@ -1,51 +0,0 @@ -from rlp import Serializable -from rlp.sedes import Binary, CountableList, List as RLPList, big_endian_int, binary - -class LegacyReceipt(Serializable): - fields = ( - ('status', big_endian_int), - ('cumulativeGasUsed', big_endian_int), - ('logsBloom', Binary(256, 256)), - ('logs', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32), 4), - binary - ]))), - ) - -class EIP2930Receipt(Serializable): - fields = ( - ('status', big_endian_int), - ('cumulativeGasUsed', big_endian_int), - ('logsBloom', Binary(256, 256)), - ('logs', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32), 4), - binary - ]))), - ) - -class EIP1559Receipt(Serializable): - fields = ( - ('status', big_endian_int), - ('cumulative_transaction_gas_used', big_endian_int), - ('logs_bloom', Binary(256, 256)), - ('logs', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32), 4), - binary - ]))), - ) - -class EIP4844Receipt(Serializable): - fields = ( - ('status', big_endian_int), - ('cumulative_gas_used', big_endian_int), - ('logs_bloom', Binary(256, 256)), - ('logs', CountableList(RLPList([ - Binary(20, 20), - CountableList(Binary(32, 32), 4), - binary - ]))), - ('cumulative_data_gas_used', big_endian_int), - ) diff --git a/assets/eip-6466/helpers/recover_receipts.py b/assets/eip-6466/helpers/recover_receipts.py deleted file mode 100644 index b520893..0000000 --- a/assets/eip-6466/helpers/recover_receipts.py +++ /dev/null @@ -1,65 +0,0 @@ -from rlp import encode -from ssz_receipt_types import * -from eip2718_receipt_types import * - -def recover_legacy_receipt(receipt: Receipt) -> LegacyReceipt: - return LegacyReceipt( - status=receipt.status, - cumulativeGasUsed=receipt.cumulative_gas_used, - logsBloom=receipt.logs_bloom, - logs=[( - log.address, - log.topics, - log.data, - ) for log in receipt.logs], - ) - -def recover_eip2930_receipt(receipt: Receipt) -> EIP2930Receipt: - return EIP2930Receipt( - status=receipt.status, - cumulativeGasUsed=receipt.cumulative_gas_used, - logsBloom=receipt.logs_bloom, - logs=[( - log.address, - log.topics, - log.data, - ) for log in receipt.logs], - ) - -def recover_eip1559_receipt(receipt: Receipt) -> EIP1559Receipt: - return EIP1559Receipt( - status=receipt.status, - cumulative_transaction_gas_used=receipt.cumulative_gas_used, - logs_bloom=receipt.logs_bloom, - logs=[( - log.address, - log.topics, - log.data, - ) for log in receipt.logs], - ) - -def recover_eip4844_receipt(receipt: Receipt) -> EIP4844Receipt: - return EIP4844Receipt( - status=receipt.status, - cumulative_gas_used=receipt.cumulative_gas_used, - logs_bloom=receipt.logs_bloom, - logs=[( - log.address, - log.topics, - log.data, - ) for log in receipt.logs], - cumulative_data_gas_used=receipt.cumulative_data_gas_used.get(), - ) - -def recover_encoded_receipt(receipt: Receipt) -> bytes: - tx_type = receipt.tx_type - - if tx_type == TRANSACTION_TYPE_EIP4844: - return bytes([0x05]) + encode(recover_eip4844_receipt(receipt)) - if tx_type == TRANSACTION_TYPE_EIP1559: - return bytes([0x02]) + encode(recover_eip1559_receipt(receipt)) - if tx_type == TRANSACTION_TYPE_EIP2930: - return bytes([0x01]) + encode(recover_eip2930_receipt(receipt)) - if tx_type == TRANSACTION_TYPE_LEGACY: - return encode(recover_legacy_receipt(receipt)) - assert False diff --git a/assets/eip-6466/helpers/ssz_receipt_types.py b/assets/eip-6466/helpers/ssz_receipt_types.py deleted file mode 100644 index 5089303..0000000 --- a/assets/eip-6466/helpers/ssz_receipt_types.py +++ /dev/null @@ -1,42 +0,0 @@ -from os import path as os_path -from sys import path -path.append(os_path.dirname(os_path.dirname(os_path.realpath(__file__)))) -path.append('../../eip-6475') - -from optional import Optional -from remerkleable.basic import uint8, uint64, uint256 -from remerkleable.byte_arrays import ByteVector, Bytes32 -from remerkleable.complex import Container, List - -class ExecutionAddress(ByteVector[20]): - pass - -class TransactionType(uint8): - pass - -TRANSACTION_TYPE_LEGACY = TransactionType(0x00) -TRANSACTION_TYPE_EIP2930 = TransactionType(0x01) -TRANSACTION_TYPE_EIP1559 = TransactionType(0x02) -TRANSACTION_TYPE_EIP4844 = TransactionType(0x05) - -BYTES_PER_LOGS_BLOOM = uint64(2**8) - -class Topic(Bytes32): - pass - -MAX_TOPICS_PER_LOG = uint64(2**2) -MAX_LOG_DATA_SIZE = uint64(2**24) -MAX_LOGS_PER_RECEIPT = uint64(2**20) - -class ReceiptLog(Container): - address: ExecutionAddress - topics: List[Topic, MAX_TOPICS_PER_LOG] - data: ByteVector[MAX_LOG_DATA_SIZE] - -class Receipt(Container): - status: uint256 # EIP-658 - cumulative_gas_used: uint64 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - logs: List[ReceiptLog, MAX_LOGS_PER_RECEIPT] - tx_type: TransactionType - cumulative_data_gas_used: Optional[uint64] diff --git a/assets/eip-6475/optional.py b/assets/eip-6475/optional.py deleted file mode 100644 index 32afa79..0000000 --- a/assets/eip-6475/optional.py +++ /dev/null @@ -1,185 +0,0 @@ -from typing import Any, BinaryIO, Optional as PyOptional, TypeVar, Type, cast -from remerkleable.basic import uint256 -from remerkleable.complex import MonoSubtreeView -from remerkleable.core import BasicView, View, ViewHook -from remerkleable.tree import Gindex, Node, PairNode, get_depth, subtree_fill_to_contents, zero_node - -T = TypeVar('T', bound="Optional") - -class Optional(MonoSubtreeView): - __slots__ = () - - def __new__(cls, value: PyOptional[Type[T]] = None, backing: PyOptional[Node] = None, hook: PyOptional[ViewHook] = None, **kwargs): - if backing is not None: - if value is not None: - raise Exception("cannot have both a backing and a value to init Optional") - return super().__new__(cls, backing=backing, hook=hook, **kwargs) - - elem_cls = cls.element_cls() - assert cls.limit() == 1 - input_views = [] - if value is not None: - if isinstance(value, View): - input_views.append(value) - else: - input_views.append(elem_cls.coerce_view(value)) - input_nodes = cls.views_into_chunks(input_views) - contents = subtree_fill_to_contents(input_nodes, cls.contents_depth()) - backing = PairNode(contents, uint256(len(input_views)).get_backing()) - return super().__new__(cls, backing=backing, hook=hook, **kwargs) - - def __class_getitem__(cls, element_type) -> Type["Optional"]: - if element_type.min_byte_length() == 0: - raise Exception(f"Invalid option type: ${element_type}") - - limit = 1 - contents_depth = get_depth(limit) - packed = isinstance(element_type, BasicView) - - class SpecialOptionView(Optional): - @classmethod - def is_packed(cls) -> bool: - return packed - - @classmethod - def contents_depth(cls) -> int: - return contents_depth - - @classmethod - def element_cls(cls) -> Type[View]: - return element_type - - @classmethod - def limit(cls) -> int: - return limit - - SpecialOptionView.__name__ = SpecialOptionView.type_repr() - return SpecialOptionView - - def length(self) -> int: - ll_node = super().get_backing().get_right() - ll = cast(uint256, uint256.view_from_backing(node=ll_node, hook=None)) - return int(ll) - - def value_byte_length(self) -> int: - if self.length() == 0: - return 0 - else: - elem_cls = self.__class__.element_cls() - if elem_cls.is_fixed_byte_length(): - return elem_cls.type_byte_length() - else: - return cast(View, el).value_byte_length() - - def get(self) -> PyOptional[View]: - if self.length() == 0: - return None - else: - return super().get(0) - - def set(self, v: PyOptional[View]) -> None: - if v is None: - if self.length() == 0: - return - i = 0 - target = to_gindex(i, self.__class__.tree_depth()) - set_last = self.get_backing().setter(target) - next_backing = set_last(zero_node(0)) - can_summarize = (target & 1) == 0 - if can_summarize: - while (target & 1) == 0 and target != 0b10: - target >>= 1 - summary_fn = next_backing.summarize_into(target) - next_backing = summary_fn() - set_length = next_backing.rebind_right - new_length = uint256(i).get_backing() - next_backing = set_length(new_length) - self.set_backing(next_backing) - else: - if self.length() == 1: - super().set(0, v) - return - i = 0 - elem_type: Type[View] = self.__class__.element_cls() - if not isinstance(v, elem_type): - v = elem_type.coerce_view(v) - target = to_gindex(i, self.__class__.tree_depth()) - set_last = self.get_backing().setter(target, expand=True) - next_backing = set_last(v.get_backing()) - set_length = next_backing.rebind_right - new_length = uint256(i + 1).get_backing() - next_backing = set_length(new_length) - self.set_backing(next_backing) - - def __repr__(self): - value = self.get() - if value is None: - return f"{self.type_repr()}(None)" - else: - return f"{self.type_repr()}(Some({repr(value)}))" - - @classmethod - def type_repr(cls) -> str: - return f"Optional[{cls.element_cls().__name__}]" - - @classmethod - def is_packed(cls) -> bool: - raise NotImplementedError - - @classmethod - def contents_depth(cls) -> int: - raise NotImplementedError - - @classmethod - def tree_depth(cls) -> int: - return cls.contents_depth() + 1 # 1 extra for length mix-in - - @classmethod - def limit(cls) -> int: - raise NotImplementedError - - @classmethod - def deserialize(cls: Type[T], stream: BinaryIO, scope: int) -> Type[T]: - if scope == 0: - return cls() - else: - return cls(cls.element_cls().deserialize(stream, scope)) - - def serialize(self, stream: BinaryIO) -> int: - v = self.get() - if v is None: - return 0 - else: - return v.serialize(stream) - - @classmethod - def navigate_type(cls, key: Any) -> Type[View]: - if key >= cls.limit(): - raise KeyError - return super().navigate_type(key) - - @classmethod - def key_to_static_gindex(cls, key: Any) -> Gindex: - if key == '__is_some__': - return RIGHT_GINDEX - if key >= cls.limit(): - raise KeyError - return super().key_to_static_gindex(key) - - @classmethod - def default_node(cls) -> Node: - return PairNode(zero_node(cls.contents_depth()), zero_node(0)) # mix-in 0 as list length - - @classmethod - def is_fixed_byte_length(cls) -> bool: - return False - - @classmethod - def min_byte_length(cls) -> int: - return 0 - - @classmethod - def max_byte_length(cls) -> int: - elem_cls = cls.element_cls() - bytes_per_elem = elem_cls.max_byte_length() - return bytes_per_elem diff --git a/assets/eip-6475/tests.py b/assets/eip-6475/tests.py deleted file mode 100644 index dcdbc9a..0000000 --- a/assets/eip-6475/tests.py +++ /dev/null @@ -1,69 +0,0 @@ -from optional import Optional -from remerkleable.basic import boolean, uint8, uint16, uint32, uint64, uint128, uint256 -from remerkleable.bitfields import Bitlist, Bitvector -from remerkleable.complex import Container, List, Vector -from remerkleable.union import Union - -def do_test(value): - v = value.get() - if v is None: - assert value.encode_bytes() == b'' - assert value.hash_tree_root() == List[value.__class__, 1]().hash_tree_root() - else: - assert value.encode_bytes() == v.encode_bytes() - assert value.hash_tree_root() == List[value.__class__, 1](v).hash_tree_root() - assert value.__class__.decode_bytes(value.encode_bytes()) == value - -if __name__ == '__main__': - do_test(Optional[uint8](None)) - do_test(Optional[uint8](8)) - - do_test(Optional[uint16](None)) - do_test(Optional[uint16](16)) - - do_test(Optional[uint32](None)) - do_test(Optional[uint32](32)) - - do_test(Optional[uint64](None)) - do_test(Optional[uint64](64)) - - do_test(Optional[uint128](None)) - do_test(Optional[uint128](128)) - - do_test(Optional[uint256](None)) - do_test(Optional[uint256](256)) - - do_test(Optional[boolean](None)) - do_test(Optional[boolean](True)) - - class Foo(Container): - a: uint64 - b: Optional[uint32] - c: Optional[uint16] - - do_test(Optional[Foo](None)) - do_test(Optional[Foo](Foo(a=64))) - do_test(Optional[Foo](Foo(a=64, b=Optional[uint32](32)))) - do_test(Optional[Foo](Foo(a=64, b=Optional[uint32](32), c=Optional[uint16](16)))) - - do_test(Optional[Vector[uint64, 1]](None)) - do_test(Optional[Vector[uint64, 1]](Vector[uint64, 1](64))) - do_test(Optional[Vector[uint64, 5]](None)) - do_test(Optional[Vector[uint64, 5]](Vector[uint64, 5](64, 64, 64, 64, 64))) - - do_test(Optional[Bitvector[1]](None)) - do_test(Optional[Bitvector[1]](Bitvector[1](True))) - do_test(Optional[Bitvector[9]](None)) - do_test(Optional[Bitvector[9]](Bitvector[9](True, True, True, True, False, True, True, True, True))) - - do_test(Optional[Bitlist[0]](None)) - do_test(Optional[Bitlist[0]](Bitlist[0]())) - do_test(Optional[Bitlist[1]](None)) - do_test(Optional[Bitlist[1]](Bitlist[1](True))) - do_test(Optional[Bitlist[9]](None)) - do_test(Optional[Bitlist[9]](Bitlist[9](True))) - - do_test(Optional[Union[None, uint64, uint32]](None)) - do_test(Optional[Union[None, uint64, uint32]](Union[None, uint64, uint32](selector=0))) - do_test(Optional[Union[None, uint64, uint32]](Union[None, uint64, uint32](selector=1, value=64))) - do_test(Optional[Union[None, uint64, uint32]](Union[None, uint64, uint32](selector=2, value=32))) diff --git a/assets/eip-6493/security/collision.py b/assets/eip-6493/security/collision.py deleted file mode 100644 index 924dfe7..0000000 --- a/assets/eip-6493/security/collision.py +++ /dev/null @@ -1,32 +0,0 @@ -from remerkleable.byte_arrays import ByteList, ByteVector -from remerkleable.complex import Container - -class Signature(ByteVector[65]): - pass - -class FooTransaction(Container): - x: ByteVector[73] - -class FooSignedTransaction(Container): - message: FooTransaction - signature: Signature - -class BarTransaction(Container): - x: ByteList[65] - -class BarSignedTransaction(Container): - message: BarTransaction - signature: Signature - -FooSignedTransaction( - message=FooTransaction( - x=bytes.fromhex('45000000bf9f6e691dc8d90c41308b3155baf8095a3b10b4ddf8cac808cb755ba872e14b5ee6f49aa43f89715d24235bf49ffa6cd30179e44e360fc0458bc5b4f3458fc31404000000'), - ), - signature=bytes.fromhex('a70279ba3b0c56b04cbaeb805e5f36e9214427f2df26d906e0e05cd013cb25463dec281a060b3df5d240c1354113fbdbac70d2f1392002dbd0b7a4f991a0996001'), -).encode_bytes() == \ -BarSignedTransaction( - message=BarTransaction( - x=bytes.fromhex('a70279ba3b0c56b04cbaeb805e5f36e9214427f2df26d906e0e05cd013cb25463dec281a060b3df5d240c1354113fbdbac70d2f1392002dbd0b7a4f991a0996001'), - ), - signature=bytes.fromhex('f3d2ea8ea566a9ce680fc638a00127f183f22c98eceb0d909b7c8660faca3a46648a9da6f40f515cb39d18dbf5f4526a052736832a8bb6234b27e9e536a531a400'), -).encode_bytes() diff --git a/assets/eip-6551/diagram.png b/assets/eip-6551/diagram.png deleted file mode 100644 index 38d746d..0000000 Binary files a/assets/eip-6551/diagram.png and /dev/null differ diff --git a/assets/eip-6662/auth-flow.png b/assets/eip-6662/auth-flow.png deleted file mode 100644 index b21206c..0000000 Binary files a/assets/eip-6662/auth-flow.png and /dev/null differ diff --git a/assets/eip-6672/contracts/ERC6672.sol b/assets/eip-6672/contracts/ERC6672.sol deleted file mode 100644 index 7d36c89..0000000 --- a/assets/eip-6672/contracts/ERC6672.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "./interfaces/IERC6672.sol"; - -contract ERC6672 is ERC721, IERC6672 { - using EnumerableSet for EnumerableSet.Bytes32Set; - - bytes4 public constant IERC6672_ID = type(IERC6672).interfaceId; - - mapping(address => mapping(uint256 => mapping(bytes32 => bool))) redemptionStatus; - mapping(address => mapping(uint256 => mapping(bytes32 => string))) public memos; - mapping(address => mapping(uint256 => EnumerableSet.Bytes32Set)) redemptions; - - constructor() ERC721("Multiple RedeemableNFT", "mrNFT") {} - - function isRedeemed(address _operator, bytes32 _redemptionId, uint256 _tokenId) external view returns (bool) { - return _isRedeemed(_operator, _redemptionId, _tokenId); - } - - function getRedemptionIds(address _operator, uint256 _tokenId) external view returns (bytes32[] memory) { - require(redemptions[_operator][_tokenId].length() > 0, "ERC6672: token doesn't have any redemptions."); - return redemptions[_operator][_tokenId].values(); - } - - function redeem(bytes32 _redemptionId, uint256 _tokenId, string memory _memo) external { - address _operator = msg.sender; - require(!_isRedeemed(_operator, _redemptionId, _tokenId), "ERC6672: token already redeemed."); - _update(_operator, _redemptionId, _tokenId, _memo, true); - redemptions[_operator][_tokenId].add(_redemptionId); - } - - function cancel(bytes32 _redemptionId, uint256 _tokenId, string memory _memo) external { - address _operator = msg.sender; - require(_isRedeemed(_operator, _redemptionId, _tokenId), "ERC6672: token doesn't redeemed."); - _update(_operator, _redemptionId, _tokenId, _memo, false); - _removeRedemption(_operator, _redemptionId, _tokenId); - } - - function _isRedeemed(address _operator, bytes32 _redemptionId, uint256 _tokenId) internal view returns (bool) { - require(_exists(_tokenId), "ERC6672: token doesn't exists."); - return redemptionStatus[_operator][_tokenId][_redemptionId]; - } - - function _update(address _operator, bytes32 _redemptionId, uint256 _tokenId, string memory _memo, bool isRedeemed_) internal { - redemptionStatus[_operator][_tokenId][_redemptionId] = isRedeemed_; - memos[_operator][_tokenId][_redemptionId] = _memo; - } - - function _removeRedemption(address _operator, bytes32 _redemptionId, uint256 _tokenId) internal { - redemptions[_operator][_tokenId].remove(_redemptionId); - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) { - return - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC6672).interfaceId || - super.supportsInterface(interfaceId); - } -} \ No newline at end of file diff --git a/assets/eip-6672/contracts/interfaces/IERC6672.sol b/assets/eip-6672/contracts/interfaces/IERC6672.sol deleted file mode 100644 index bdbc9e7..0000000 --- a/assets/eip-6672/contracts/interfaces/IERC6672.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -interface IERC6672 is IERC721 { - function isRedeemed(address _operator, bytes32 _redemptionId, uint256 _tokenId) external view returns (bool); - function getRedemptionIds(address _operator, uint256 _tokenId) external view returns (bytes32[] memory); - function redeem(bytes32 _redemptionId, uint256 _tokenId, string memory _memo) external; - function cancel(bytes32 _redemptionId, uint256 _tokenId, string memory _memo) external; -} \ No newline at end of file diff --git a/assets/eip-6789/MetaMask_ManaLimit.png b/assets/eip-6789/MetaMask_ManaLimit.png deleted file mode 100644 index 639cbb4..0000000 Binary files a/assets/eip-6789/MetaMask_ManaLimit.png and /dev/null differ diff --git a/assets/eip-712/Example.js b/assets/eip-712/Example.js deleted file mode 100644 index 2fa7658..0000000 --- a/assets/eip-712/Example.js +++ /dev/null @@ -1,153 +0,0 @@ -// using ethereumjs-util 7.1.3 -const ethUtil = require('ethereumjs-util'); - -// using ethereumjs-abi 0.6.9 -const abi = require('ethereumjs-abi'); - -// using chai 4.3.4 -const chai = require('chai'); - -const typedData = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' } - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' } - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 1, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - from: { - name: 'Cow', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - to: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'Hello, Bob!', - }, -}; - -const types = typedData.types; - -// Recursively finds all the dependencies of a type -function dependencies(primaryType, found = []) { - if (found.includes(primaryType)) { - return found; - } - if (types[primaryType] === undefined) { - return found; - } - found.push(primaryType); - for (let field of types[primaryType]) { - for (let dep of dependencies(field.type, found)) { - if (!found.includes(dep)) { - found.push(dep); - } - } - } - return found; -} - -function encodeType(primaryType) { - // Get dependencies primary first, then alphabetical - let deps = dependencies(primaryType); - deps = deps.filter(t => t != primaryType); - deps = [primaryType].concat(deps.sort()); - - // Format as a string with fields - let result = ''; - for (let type of deps) { - result += `${type}(${types[type].map(({ name, type }) => `${type} ${name}`).join(',')})`; - } - return result; -} - -function typeHash(primaryType) { - return ethUtil.keccakFromString(encodeType(primaryType), 256); -} - -function encodeData(primaryType, data) { - let encTypes = []; - let encValues = []; - - // Add typehash - encTypes.push('bytes32'); - encValues.push(typeHash(primaryType)); - - // Add field contents - for (let field of types[primaryType]) { - let value = data[field.name]; - if (field.type == 'string' || field.type == 'bytes') { - encTypes.push('bytes32'); - value = ethUtil.keccakFromString(value, 256); - encValues.push(value); - } else if (types[field.type] !== undefined) { - encTypes.push('bytes32'); - value = ethUtil.keccak256(encodeData(field.type, value)); - encValues.push(value); - } else if (field.type.lastIndexOf(']') === field.type.length - 1) { - throw 'TODO: Arrays currently unimplemented in encodeData'; - } else { - encTypes.push(field.type); - encValues.push(value); - } - } - - return abi.rawEncode(encTypes, encValues); -} - -function structHash(primaryType, data) { - return ethUtil.keccak256(encodeData(primaryType, data)); -} - -function signHash() { - return ethUtil.keccak256( - Buffer.concat([ - Buffer.from('1901', 'hex'), - structHash('EIP712Domain', typedData.domain), - structHash(typedData.primaryType, typedData.message), - ]), - ); -} - -const privateKey = ethUtil.keccakFromString('cow', 256); -const address = ethUtil.privateToAddress(privateKey); -const sig = ethUtil.ecsign(signHash(), privateKey); - -const expect = chai.expect; -expect(encodeType('Mail')).to.equal('Mail(Person from,Person to,string contents)Person(string name,address wallet)'); -expect(ethUtil.bufferToHex(typeHash('Mail'))).to.equal( - '0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2', -); -expect(ethUtil.bufferToHex(encodeData(typedData.primaryType, typedData.message))).to.equal( - '0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8', -); -expect(ethUtil.bufferToHex(structHash(typedData.primaryType, typedData.message))).to.equal( - '0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e', -); -expect(ethUtil.bufferToHex(structHash('EIP712Domain', typedData.domain))).to.equal( - '0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f', -); -expect(ethUtil.bufferToHex(signHash())).to.equal('0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2'); -expect(ethUtil.bufferToHex(address)).to.equal('0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826'); -expect(sig.v).to.equal(28); -expect(ethUtil.bufferToHex(sig.r)).to.equal('0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d'); -expect(ethUtil.bufferToHex(sig.s)).to.equal('0x07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562'); diff --git a/assets/eip-712/Example.sol b/assets/eip-712/Example.sol deleted file mode 100644 index dea246a..0000000 --- a/assets/eip-712/Example.sol +++ /dev/null @@ -1,106 +0,0 @@ -pragma solidity ^0.4.24; - -contract Example { - - struct EIP712Domain { - string name; - string version; - uint256 chainId; - address verifyingContract; - } - - struct Person { - string name; - address wallet; - } - - struct Mail { - Person from; - Person to; - string contents; - } - - bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ); - - bytes32 constant PERSON_TYPEHASH = keccak256( - "Person(string name,address wallet)" - ); - - bytes32 constant MAIL_TYPEHASH = keccak256( - "Mail(Person from,Person to,string contents)Person(string name,address wallet)" - ); - - bytes32 DOMAIN_SEPARATOR; - - constructor () public { - DOMAIN_SEPARATOR = hash(EIP712Domain({ - name: "Ether Mail", - version: '1', - chainId: 1, - // verifyingContract: this - verifyingContract: 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC - })); - } - - function hash(EIP712Domain eip712Domain) internal pure returns (bytes32) { - return keccak256(abi.encode( - EIP712DOMAIN_TYPEHASH, - keccak256(bytes(eip712Domain.name)), - keccak256(bytes(eip712Domain.version)), - eip712Domain.chainId, - eip712Domain.verifyingContract - )); - } - - function hash(Person person) internal pure returns (bytes32) { - return keccak256(abi.encode( - PERSON_TYPEHASH, - keccak256(bytes(person.name)), - person.wallet - )); - } - - function hash(Mail mail) internal pure returns (bytes32) { - return keccak256(abi.encode( - MAIL_TYPEHASH, - hash(mail.from), - hash(mail.to), - keccak256(bytes(mail.contents)) - )); - } - - function verify(Mail mail, uint8 v, bytes32 r, bytes32 s) internal view returns (bool) { - // Note: we need to use `encodePacked` here instead of `encode`. - bytes32 digest = keccak256(abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - hash(mail) - )); - return ecrecover(digest, v, r, s) == mail.from.wallet; - } - - function test() public view returns (bool) { - // Example signed message - Mail memory mail = Mail({ - from: Person({ - name: "Cow", - wallet: 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 - }), - to: Person({ - name: "Bob", - wallet: 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB - }), - contents: "Hello, Bob!" - }); - uint8 v = 28; - bytes32 r = 0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d; - bytes32 s = 0x07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562; - - assert(DOMAIN_SEPARATOR == 0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f); - assert(hash(mail) == 0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e); - assert(verify(mail, v, r, s)); - return true; - } -} diff --git a/assets/eip-712/eth_sign.png b/assets/eip-712/eth_sign.png deleted file mode 100644 index d52d431..0000000 Binary files a/assets/eip-712/eth_sign.png and /dev/null differ diff --git a/assets/eip-712/eth_signTypedData.png b/assets/eip-712/eth_signTypedData.png deleted file mode 100644 index 3802129..0000000 Binary files a/assets/eip-712/eth_signTypedData.png and /dev/null differ diff --git a/assets/eip-725/ERC725.sol b/assets/eip-725/ERC725.sol deleted file mode 100644 index fbab76f..0000000 --- a/assets/eip-725/ERC725.sol +++ /dev/null @@ -1,1283 +0,0 @@ -pragma solidity ^0.8.8; - -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding [ERC165] standard. - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -contract ERC165 is IERC165 { - /** - * @inheritdoc IERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return interfaceId == type(IERC165).interfaceId; - } -} - -contract ERC173 { - address private _owner; - - event OwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - constructor() { - _transferOwnership(msg.sender); - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - _checkOwner(); - _; - } - - /** - * @dev Returns the address of the current owner. - */ - function owner() public view virtual returns (address) { - return _owner; - } - - /** - * @dev Throws if the sender is not the owner. - */ - function _checkOwner() internal view virtual { - require(owner() == msg.sender, "Ownable: caller is not the owner"); - } - - /** - * @dev Leaves the contract without owner. It will not be possible to call - * `onlyOwner` functions anymore. Can only be called by the current owner. - * - * NOTE: Renouncing ownership will leave the contract without an owner, - * thereby removing any functionality that is only available to the owner. - */ - function renounceOwnership() public virtual onlyOwner { - _transferOwnership(address(0)); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Can only be called by the current owner. - */ - function transferOwnership(address newOwner) public virtual onlyOwner { - require( - newOwner != address(0), - "Ownable: new owner is the zero address" - ); - _transferOwnership(newOwner); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Internal function without access restriction. - */ - function _transferOwnership(address newOwner) internal virtual { - address oldOwner = _owner; - _owner = newOwner; - emit OwnershipTransferred(oldOwner, newOwner); - } -} - -interface IERC725X is IERC165 { - /** - * @notice Emitted when deploying a contract - * @param operationType The opcode used to deploy the contract (CREATE or CREATE2) - * @param contractAddress The created contract address - * @param value The amount of native tokens (in Wei) sent to fund the created contract address - * @param salt The salt used in case of CREATE2. Will be bytes32(0) in case of CREATE operation - */ - event ContractCreated( - uint256 indexed operationType, - address indexed contractAddress, - uint256 indexed value, - bytes32 salt - ); - - /** - * @notice Emitted when calling an address (EOA or contract) - * @param operationType The low-level call opcode used to call the `to` address (CALL, STATICALL or DELEGATECALL) - * @param target The address to call. `target` will be unused if a contract is created (operation types 1 and 2). - * @param value The amount of native tokens transferred with the call (in Wei) - * @param selector The first 4 bytes (= function selector) of the data sent with the call - */ - event Executed( - uint256 indexed operationType, - address indexed target, - uint256 indexed value, - bytes4 selector - ); - - /** - * @param operationType The operation type used: CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4 - * @param target The address of the EOA or smart contract. (unused if a contract is created via operation type 1 or 2) - * @param value The amount of native tokens to transfer (in Wei) - * @param data The call data, or the creation bytecode of the contract to deploy - * - * @dev Generic executor function to: - * - * - send native tokens to any address. - * - interact with any contract by passing an abi-encoded function call in the `data` parameter. - * - deploy a contract by providing its creation bytecode in the `data` parameter. - * - * Requirements: - * - * - SHOULD only be callable by the owner of the contract set via ERC173. - * - if a `value` is provided, the contract MUST have at least this amount in its balance to execute successfully. - * - if the operation type is STATICCALL or DELEGATECALL, `value` SHOULD be 0. - * - `target` SHOULD be address(0) when deploying a contract. - * - * Emits an {Executed} event, when a call is made with `operationType` 0 (CALL), 3 (STATICCALL) or 4 (DELEGATECALL) - * Emits a {ContractCreated} event, when deploying a contract with `operationType` 1 (CREATE) or 2 (CREATE2) - */ - function execute( - uint256 operationType, - address target, - uint256 value, - bytes memory data - ) external payable returns (bytes memory); - - /** - * @param operationsType The list of operations type used: CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4 - * @param targets The list of addresses to call. `targets` will be unused if a contract is created (operation types 1 and 2). - * @param values The list of native token amounts to transfer (in Wei) - * @param datas The list of call data, or the creation bytecode of the contract to deploy - * - * @dev Generic batch executor function to: - * - * - send native tokens to any address. - * - interact with any contract by passing an abi-encoded function call in the `datas` parameter. - * - deploy a contract by providing its creation bytecode in the `datas` parameter. - * - * Requirements: - * - * - The length of the parameters provided MUST be equal - * - SHOULD only be callable by the owner of the contract set via ERC173. - * - if a `values` is provided, the contract MUST have at least this amount in its balance to execute successfully. - * - if the operation type is STATICCALL or DELEGATECALL, `values` SHOULD be 0. - * - `targets` SHOULD be address(0) when deploying a contract. - * - * Emits an {Executed} event, when a call is made with `operationType` 0 (CALL), 3 (STATICCALL) or 4 (DELEGATECALL) - * Emits a {ContractCreated} event, when deploying a contract with `operationType` 1 (CREATE) or 2 (CREATE2) - */ - function execute( - uint256[] memory operationsType, - address[] memory targets, - uint256[] memory values, - bytes[] memory datas - ) external payable returns (bytes[] memory); -} - -// ERC165 INTERFACE IDs -bytes4 constant _INTERFACEID_ERC725X = 0x570ef073; - -// ERC725X OPERATION TYPES -uint256 constant OPERATION_0_CALL = 0; -uint256 constant OPERATION_1_CREATE = 1; -uint256 constant OPERATION_2_CREATE2 = 2; -uint256 constant OPERATION_3_STATICCALL = 3; -uint256 constant OPERATION_4_DELEGATECALL = 4; - -/** - * @dev reverts when trying to send more native tokens `value` than available in current `balance`. - * @param balance the balance of the ERC725X contract. - * @param value the amount of native tokens sent via `ERC725X.execute(...)`. - */ -error ERC725X_InsufficientBalance(uint256 balance, uint256 value); - -/** - * @dev reverts when the `operationTypeProvided` is none of the default operation types available. - * (CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4) - */ -error ERC725X_UnknownOperationType(uint256 operationTypeProvided); - -/** - * @dev the `value` parameter (= sending native tokens) is not allowed when making a staticcall - * via `ERC725X.execute(...)` because sending native tokens is a state changing operation. - */ -error ERC725X_MsgValueDisallowedInStaticCall(); - -/** - * @dev the `value` parameter (= sending native tokens) is not allowed when making a delegatecall - * via `ERC725X.execute(...)` because msg.value is persisting. - */ -error ERC725X_MsgValueDisallowedInDelegateCall(); - -/** - * @dev reverts when passing a `to` address while deploying a contract va `ERC725X.execute(...)` - * whether using operation type 1 (CREATE) or 2 (CREATE2). - */ -error ERC725X_CreateOperationsRequireEmptyRecipientAddress(); - -/** - * @dev reverts when contract deployment via `ERC725X.execute(...)` failed. - * whether using operation type 1 (CREATE) or 2 (CREATE2). - */ -error ERC725X_ContractDeploymentFailed(); - -/** - * @dev reverts when no contract bytecode was provided as parameter when trying to deploy a contract - * via `ERC725X.execute(...)`, whether using operation type 1 (CREATE) or 2 (CREATE2). - */ -error ERC725X_NoContractBytecodeProvided(); - -/** - * @dev reverts when there is not the same number of operation, to addresses, value, and data. - */ -error ERC725X_ExecuteParametersLengthMismatch(); - -contract ERC725X is ERC173, ERC165, IERC725X { - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(IERC165, ERC165) - returns (bool) - { - return - interfaceId == _INTERFACEID_ERC725X || - super.supportsInterface(interfaceId); - } - - /** - * @inheritdoc IERC725X - */ - function execute( - uint256 operationType, - address target, - uint256 value, - bytes memory data - ) public payable virtual onlyOwner returns (bytes memory) { - if (address(this).balance < value) { - revert ERC725X_InsufficientBalance(address(this).balance, value); - } - return _execute(operationType, target, value, data); - } - - /** - * @inheritdoc IERC725X - */ - function execute( - uint256[] memory operationsType, - address[] memory targets, - uint256[] memory values, - bytes[] memory datas - ) public payable virtual onlyOwner returns (bytes[] memory result) { - if ( - operationsType.length != targets.length || - (targets.length != values.length || values.length != datas.length) - ) revert ERC725X_ExecuteParametersLengthMismatch(); - - result = new bytes[](operationsType.length); - for (uint256 i = 0; i < operationsType.length; i++) { - if (address(this).balance < values[i]) - revert ERC725X_InsufficientBalance( - address(this).balance, - values[i] - ); - - result[i] = _execute( - operationsType[i], - targets[i], - values[i], - datas[i] - ); - } - } - - function _execute( - uint256 operationType, - address target, - uint256 value, - bytes memory data - ) internal virtual returns (bytes memory) { - // CALL - if (operationType == OPERATION_0_CALL) { - return _executeCall(target, value, data); - } - - // Deploy with CREATE - if (operationType == uint256(OPERATION_1_CREATE)) { - if (target != address(0)) - revert ERC725X_CreateOperationsRequireEmptyRecipientAddress(); - return _deployCreate(value, data); - } - - // Deploy with CREATE2 - if (operationType == uint256(OPERATION_2_CREATE2)) { - if (target != address(0)) - revert ERC725X_CreateOperationsRequireEmptyRecipientAddress(); - return _deployCreate2(value, data); - } - - // STATICCALL - if (operationType == uint256(OPERATION_3_STATICCALL)) { - if (value != 0) revert ERC725X_MsgValueDisallowedInStaticCall(); - return _executeStaticCall(target, data); - } - - // DELEGATECALL - // - // WARNING! delegatecall is a dangerous operation type! use with EXTRA CAUTION - // - // delegate allows to call another deployed contract and use its functions - // to update the state of the current calling contract. - // - // this can lead to unexpected behaviour on the contract storage, such as: - // - updating any state variables (even if these are protected) - // - update the contract owner - // - run selfdestruct in the context of this contract - // - if (operationType == uint256(OPERATION_4_DELEGATECALL)) { - if (value != 0) revert ERC725X_MsgValueDisallowedInDelegateCall(); - return _executeDelegateCall(target, data); - } - - revert ERC725X_UnknownOperationType(operationType); - } - - /** - * @dev perform low-level call (operation type = 0) - * @param target The address on which call is executed - * @param value The value to be sent with the call - * @param data The data to be sent with the call - * @return result The data from the call - */ - function _executeCall( - address target, - uint256 value, - bytes memory data - ) internal virtual returns (bytes memory result) { - emit Executed(OPERATION_0_CALL, target, value, bytes4(data)); - - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = target.call{value: value}( - data - ); - result = Address.verifyCallResult( - success, - returnData, - "ERC725X: Unknown Error" - ); - } - - /** - * @dev perform low-level staticcall (operation type = 3) - * @param target The address on which staticcall is executed - * @param data The data to be sent with the staticcall - * @return result The data returned from the staticcall - */ - function _executeStaticCall(address target, bytes memory data) - internal - virtual - returns (bytes memory result) - { - emit Executed(OPERATION_3_STATICCALL, target, 0, bytes4(data)); - - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = target.staticcall(data); - result = Address.verifyCallResult( - success, - returnData, - "ERC725X: Unknown Error" - ); - } - - /** - * @dev perform low-level delegatecall (operation type = 4) - * @param target The address on which delegatecall is executed - * @param data The data to be sent with the delegatecall - * @return result The data returned from the delegatecall - */ - function _executeDelegateCall(address target, bytes memory data) - internal - virtual - returns (bytes memory result) - { - emit Executed(OPERATION_4_DELEGATECALL, target, 0, bytes4(data)); - - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = target.delegatecall(data); - result = Address.verifyCallResult( - success, - returnData, - "ERC725X: Unknown Error" - ); - } - - /** - * @dev deploy a contract using the CREATE opcode (operation type = 1) - * @param value The value to be sent to the contract created - * @param creationCode The contract creation bytecode to deploy appended with the constructor argument(s) - * @return newContract The address of the contract created as bytes - */ - function _deployCreate(uint256 value, bytes memory creationCode) - internal - virtual - returns (bytes memory newContract) - { - if (creationCode.length == 0) { - revert ERC725X_NoContractBytecodeProvided(); - } - - address contractAddress; - // solhint-disable no-inline-assembly - assembly { - contractAddress := create( - value, - add(creationCode, 0x20), - mload(creationCode) - ) - } - - if (contractAddress == address(0)) { - revert ERC725X_ContractDeploymentFailed(); - } - - newContract = abi.encodePacked(contractAddress); - emit ContractCreated( - OPERATION_1_CREATE, - contractAddress, - value, - bytes32(0) - ); - } - - /** - * @dev deploy a contract using the CREATE2 opcode (operation type = 2) - * @param value The value to be sent to the contract created - * @param creationCode The contract creation bytecode to deploy appended with the constructor argument(s) and a bytes32 salt - * @return newContract The address of the contract created as bytes - */ - function _deployCreate2(uint256 value, bytes memory creationCode) - internal - virtual - returns (bytes memory newContract) - { - bytes32 salt = BytesLib.toBytes32( - creationCode, - creationCode.length - 32 - ); - bytes memory bytecode = BytesLib.slice( - creationCode, - 0, - creationCode.length - 32 - ); - - address contractAddress; - require( - address(this).balance >= value, - "Create2: insufficient balance" - ); - require(creationCode.length != 0, "Create2: bytecode length is zero"); - /// @solidity memory-safe-assembly - assembly { - contractAddress := create2( - value, - add(bytecode, 0x20), - mload(bytecode), - salt - ) - } - require(contractAddress != address(0), "Create2: Failed on deploy"); - - newContract = abi.encodePacked(contractAddress); - emit ContractCreated(OPERATION_2_CREATE2, contractAddress, value, salt); - } -} - -/** - * @title The interface for ERC725Y General data key/value store - * @dev ERC725Y provides the ability to set arbitrary data key/value pairs that can be changed over time - * It is intended to standardise certain data key/value pairs to allow automated read and writes - * from/to the contract storage - */ -interface IERC725Y is IERC165 { - /** - * @notice Emitted when data at a key is changed - * @param dataKey The data key which data value is set - * @param dataValue The data value to set - */ - event DataChanged(bytes32 indexed dataKey, bytes dataValue); - - /** - * @notice Gets singular data at a given `dataKey` - * @param dataKey The key which value to retrieve - * @return dataValue The data stored at the key - */ - function getData(bytes32 dataKey) - external - view - returns (bytes memory dataValue); - - /** - * @notice Gets array of data for multiple given keys - * @param dataKeys The array of keys which values to retrieve - * @return dataValues The array of data stored at multiple keys - */ - function getData(bytes32[] memory dataKeys) - external - view - returns (bytes[] memory dataValues); - - /** - * @notice Sets singular data for a given `dataKey` - * @param dataKey The key to retrieve stored value - * @param dataValue The value to set - * SHOULD only be callable by the owner of the contract set via ERC173 - * - * Emits a {DataChanged} event. - */ - function setData(bytes32 dataKey, bytes memory dataValue) external; - - /** - * @param dataKeys The array of data keys for values to set - * @param dataValues The array of values to set - * @dev Sets array of data for multiple given `dataKeys` - * SHOULD only be callable by the owner of the contract set via ERC173 - * - * Emits a {DataChanged} event. - */ - function setData(bytes32[] memory dataKeys, bytes[] memory dataValues) - external; -} - -// ERC165 INTERFACE IDs -bytes4 constant _INTERFACEID_ERC725Y = 0x714df77c; - -/** - * @dev reverts when there is not the same number of elements in the lists of data keys and data values - * when calling setData(bytes32[],bytes[]). - * @param dataKeysLength the number of data keys in the bytes32[] dataKeys - * @param dataValuesLength the number of data value in the bytes[] dataValue - */ -error ERC725Y_DataKeysValuesLengthMismatch( - uint256 dataKeysLength, - uint256 dataValuesLength -); - -contract ERC725Y is ERC173, ERC165, IERC725Y { - // overrides - - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(IERC165, ERC165) - returns (bool) - { - return - interfaceId == _INTERFACEID_ERC725Y || - super.supportsInterface(interfaceId); - } - - /** - * @dev Map the dataKeys to their dataValues - */ - mapping(bytes32 => bytes) internal _store; - - /** - * @inheritdoc IERC725Y - */ - function getData(bytes32 dataKey) - public - view - virtual - returns (bytes memory dataValue) - { - dataValue = _getData(dataKey); - } - - /** - * @inheritdoc IERC725Y - */ - function getData(bytes32[] memory dataKeys) - public - view - virtual - returns (bytes[] memory dataValues) - { - dataValues = new bytes[](dataKeys.length); - - for (uint256 i = 0; i < dataKeys.length; i++) { - dataValues[i] = _getData(dataKeys[i]); - } - - return dataValues; - } - - /** - * @inheritdoc IERC725Y - */ - function setData(bytes32 dataKey, bytes memory dataValue) - public - virtual - onlyOwner - { - _setData(dataKey, dataValue); - } - - /** - * @inheritdoc IERC725Y - */ - function setData(bytes32[] memory dataKeys, bytes[] memory dataValues) - public - virtual - onlyOwner - { - if (dataKeys.length != dataValues.length) { - revert ERC725Y_DataKeysValuesLengthMismatch( - dataKeys.length, - dataValues.length - ); - } - - for (uint256 i = 0; i < dataKeys.length; i++) { - _setData(dataKeys[i], dataValues[i]); - } - } - - function _getData(bytes32 dataKey) - internal - view - virtual - returns (bytes memory dataValue) - { - return _store[dataKey]; - } - - function _setData(bytes32 dataKey, bytes memory dataValue) - internal - virtual - { - _store[dataKey] = dataValue; - emit DataChanged(dataKey, dataValue); - } -} - -contract ERC725 is ERC725X, ERC725Y { - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC725X, ERC725Y) - returns (bool) - { - return - interfaceId == _INTERFACEID_ERC725X || - interfaceId == _INTERFACEID_ERC725Y || - super.supportsInterface(interfaceId); - } -} - -// external needed libraries - -library BytesLib { - function concat(bytes memory _preBytes, bytes memory _postBytes) - internal - pure - returns (bytes memory) - { - bytes memory tempBytes; - - assembly { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. - tempBytes := mload(0x40) - - // Store the length of the first bytes array at the beginning of - // the memory for tempBytes. - let length := mload(_preBytes) - mstore(tempBytes, length) - - // Maintain a memory counter for the current write location in the - // temp bytes array by adding the 32 bytes for the array length to - // the starting location. - let mc := add(tempBytes, 0x20) - // Stop copying when the memory counter reaches the length of the - // first bytes array. - let end := add(mc, length) - - for { - // Initialize a copy counter to the start of the _preBytes data, - // 32 bytes into its memory. - let cc := add(_preBytes, 0x20) - } lt(mc, end) { - // Increase both counters by 32 bytes each iteration. - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - // Write the _preBytes data into the tempBytes memory 32 bytes - // at a time. - mstore(mc, mload(cc)) - } - - // Add the length of _postBytes to the current length of tempBytes - // and store it as the new length in the first 32 bytes of the - // tempBytes memory. - length := mload(_postBytes) - mstore(tempBytes, add(length, mload(tempBytes))) - - // Move the memory counter back from a multiple of 0x20 to the - // actual end of the _preBytes data. - mc := end - // Stop copying when the memory counter reaches the new combined - // length of the arrays. - end := add(mc, length) - - for { - let cc := add(_postBytes, 0x20) - } lt(mc, end) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } - - // Update the free-memory pointer by padding our last write location - // to 32 bytes: add 31 bytes to the end of tempBytes to move to the - // next 32 byte block, then round down to the nearest multiple of - // 32. If the sum of the length of the two arrays is zero then add - // one before rounding down to leave a blank 32 bytes (the length block with 0). - mstore( - 0x40, - and( - add(add(end, iszero(add(length, mload(_preBytes)))), 31), - not(31) // Round down to the nearest 32 bytes. - ) - ) - } - - return tempBytes; - } - - function concatStorage(bytes storage _preBytes, bytes memory _postBytes) - internal - { - assembly { - // Read the first 32 bytes of _preBytes storage, which is the length - // of the array. (We don't need to use the offset into the slot - // because arrays use the entire slot.) - let fslot := sload(_preBytes.slot) - // Arrays of 31 bytes or less have an even value in their slot, - // while longer arrays have an odd value. The actual length is - // the slot divided by two for odd values, and the lowest order - // byte divided by two for even values. - // If the slot is even, bitwise and the slot with 255 and divide by - // two to get the length. If the slot is odd, bitwise and the slot - // with -1 and divide by two. - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) - let mlength := mload(_postBytes) - let newlength := add(slength, mlength) - // slength can contain both the length and contents of the array - // if length < 32 bytes so let's prepare for that - // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage - switch add(lt(slength, 32), lt(newlength, 32)) - case 2 { - // Since the new array still fits in the slot, we just need to - // update the contents of the slot. - // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length - sstore( - _preBytes.slot, - // all the modifications to the slot are inside this - // next block - add( - // we can just add to the slot contents because the - // bytes we want to change are the LSBs - fslot, - add( - mul( - div( - // load the bytes from memory - mload(add(_postBytes, 0x20)), - // zero all bytes to the right - exp(0x100, sub(32, mlength)) - ), - // and now shift left the number of bytes to - // leave space for the length in the slot - exp(0x100, sub(32, newlength)) - ), - // increase length by the double of the memory - // bytes length - mul(mlength, 2) - ) - ) - ) - } - case 1 { - // The stored value fits in the slot, but the combined value - // will exceed it. - // get the keccak hash to get the contents of the array - mstore(0x0, _preBytes.slot) - let sc := add(keccak256(0x0, 0x20), div(slength, 32)) - - // save new length - sstore(_preBytes.slot, add(mul(newlength, 2), 1)) - - // The contents of the _postBytes array start 32 bytes into - // the structure. Our first read should obtain the `submod` - // bytes that can fit into the unused space in the last word - // of the stored array. To get this, we read 32 bytes starting - // from `submod`, so the data we read overlaps with the array - // contents by `submod` bytes. Masking the lowest-order - // `submod` bytes allows us to add that value directly to the - // stored value. - - let submod := sub(32, slength) - let mc := add(_postBytes, submod) - let end := add(_postBytes, mlength) - let mask := sub(exp(0x100, submod), 1) - - sstore( - sc, - add( - and( - fslot, - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 - ), - and(mload(mc), mask) - ) - ) - - for { - mc := add(mc, 0x20) - sc := add(sc, 1) - } lt(mc, end) { - sc := add(sc, 1) - mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } - - mask := exp(0x100, sub(mc, end)) - - sstore(sc, mul(div(mload(mc), mask), mask)) - } - default { - // get the keccak hash to get the contents of the array - mstore(0x0, _preBytes.slot) - // Start copying to the last used word of the stored array. - let sc := add(keccak256(0x0, 0x20), div(slength, 32)) - - // save new length - sstore(_preBytes.slot, add(mul(newlength, 2), 1)) - - // Copy over the first `submod` bytes of the new data as in - // case 1 above. - let slengthmod := mod(slength, 32) - let mlengthmod := mod(mlength, 32) - let submod := sub(32, slengthmod) - let mc := add(_postBytes, submod) - let end := add(_postBytes, mlength) - let mask := sub(exp(0x100, submod), 1) - - sstore(sc, add(sload(sc), and(mload(mc), mask))) - - for { - sc := add(sc, 1) - mc := add(mc, 0x20) - } lt(mc, end) { - sc := add(sc, 1) - mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } - - mask := exp(0x100, sub(mc, end)) - - sstore(sc, mul(div(mload(mc), mask), mask)) - } - } - } - - function slice( - bytes memory _bytes, - uint256 _start, - uint256 _length - ) internal pure returns (bytes memory) { - require(_length + 31 >= _length, "slice_overflow"); - require(_bytes.length >= _start + _length, "slice_outOfBounds"); - - bytes memory tempBytes; - - assembly { - switch iszero(_length) - case 0 { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. - tempBytes := mload(0x40) - - // The first word of the slice result is potentially a partial - // word read from the original array. To read it, we calculate - // the length of that partial word and start copying that many - // bytes into the array. The first word we copy will start with - // data we don't care about, but the last `lengthmod` bytes will - // land at the beginning of the contents of the new array. When - // we're done copying, we overwrite the full first word with - // the actual length of the slice. - let lengthmod := and(_length, 31) - - // The multiplication in the next line is necessary - // because when slicing multiples of 32 bytes (lengthmod == 0) - // the following copy loop was copying the origin's length - // and then ending prematurely not copying everything it should. - let mc := add( - add(tempBytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ) - let end := add(mc, _length) - - for { - // The multiplication in the next line has the same exact purpose - // as the one above. - let cc := add( - add( - add(_bytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ), - _start - ) - } lt(mc, end) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } - - mstore(tempBytes, _length) - - //update free-memory pointer - //allocating the array padded to 32 bytes like the compiler does now - mstore(0x40, and(add(mc, 31), not(31))) - } - //if we want a zero-length slice let's just return a zero-length array - default { - tempBytes := mload(0x40) - //zero out the 32 bytes slice we are about to return - //we need to do it because Solidity does not garbage collect - mstore(tempBytes, 0) - - mstore(0x40, add(tempBytes, 0x20)) - } - } - - return tempBytes; - } - - function toAddress(bytes memory _bytes, uint256 _start) - internal - pure - returns (address) - { - require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); - address tempAddress; - - assembly { - tempAddress := div( - mload(add(add(_bytes, 0x20), _start)), - 0x1000000000000000000000000 - ) - } - - return tempAddress; - } - - function toUint8(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint8) - { - require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); - uint8 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x1), _start)) - } - - return tempUint; - } - - function toUint16(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint16) - { - require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); - uint16 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x2), _start)) - } - - return tempUint; - } - - function toUint32(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint32) - { - require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); - uint32 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x4), _start)) - } - - return tempUint; - } - - function toUint64(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint64) - { - require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); - uint64 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x8), _start)) - } - - return tempUint; - } - - function toUint96(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint96) - { - require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); - uint96 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0xc), _start)) - } - - return tempUint; - } - - function toUint128(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint128) - { - require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); - uint128 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x10), _start)) - } - - return tempUint; - } - - function toUint256(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint256) - { - require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); - uint256 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x20), _start)) - } - - return tempUint; - } - - function toBytes32(bytes memory _bytes, uint256 _start) - internal - pure - returns (bytes32) - { - require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); - bytes32 tempBytes32; - - assembly { - tempBytes32 := mload(add(add(_bytes, 0x20), _start)) - } - - return tempBytes32; - } - - function equal(bytes memory _preBytes, bytes memory _postBytes) - internal - pure - returns (bool) - { - bool success = true; - - assembly { - let length := mload(_preBytes) - - // if lengths don't match the arrays are not equal - switch eq(length, mload(_postBytes)) - case 1 { - // cb is a circuit breaker in the for loop since there's - // no said feature for inline assembly loops - // cb = 1 - don't breaker - // cb = 0 - break - let cb := 1 - - let mc := add(_preBytes, 0x20) - let end := add(mc, length) - - for { - let cc := add(_postBytes, 0x20) - // the next line is the loop condition: - // while(uint256(mc < end) + cb == 2) - } eq(add(lt(mc, end), cb), 2) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - // if any of these checks fails then arrays are not equal - if iszero(eq(mload(mc), mload(cc))) { - // unsuccess: - success := 0 - cb := 0 - } - } - } - default { - // unsuccess: - success := 0 - } - } - - return success; - } - - function equalStorage(bytes storage _preBytes, bytes memory _postBytes) - internal - view - returns (bool) - { - bool success = true; - - assembly { - // we know _preBytes_offset is 0 - let fslot := sload(_preBytes.slot) - // Decode the length of the stored array like in concatStorage(). - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) - let mlength := mload(_postBytes) - - // if lengths don't match the arrays are not equal - switch eq(slength, mlength) - case 1 { - // slength can contain both the length and contents of the array - // if length < 32 bytes so let's prepare for that - // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage - if iszero(iszero(slength)) { - switch lt(slength, 32) - case 1 { - // blank the last byte which is the length - fslot := mul(div(fslot, 0x100), 0x100) - - if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { - // unsuccess: - success := 0 - } - } - default { - // cb is a circuit breaker in the for loop since there's - // no said feature for inline assembly loops - // cb = 1 - don't breaker - // cb = 0 - break - let cb := 1 - - // get the keccak hash to get the contents of the array - mstore(0x0, _preBytes.slot) - let sc := keccak256(0x0, 0x20) - - let mc := add(_postBytes, 0x20) - let end := add(mc, mlength) - - // the next line is the loop condition: - // while(uint256(mc < end) + cb == 2) - for { - - } eq(add(lt(mc, end), cb), 2) { - sc := add(sc, 1) - mc := add(mc, 0x20) - } { - if iszero(eq(sload(sc), mload(mc))) { - // unsuccess: - success := 0 - cb := 0 - } - } - } - } - } - default { - // unsuccess: - success := 0 - } - } - - return success; - } -} - -library Address { - /** - * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason using the provided one. - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} diff --git a/assets/eip-747/add-token-prompt.gif b/assets/eip-747/add-token-prompt.gif deleted file mode 100644 index 6d85498..0000000 Binary files a/assets/eip-747/add-token-prompt.gif and /dev/null differ diff --git a/assets/eip-747/add-token-prompt2.gif b/assets/eip-747/add-token-prompt2.gif deleted file mode 100644 index cf7cb7c..0000000 Binary files a/assets/eip-747/add-token-prompt2.gif and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-1024px.png deleted file mode 100644 index e9c0e6e..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-192px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-192px.png deleted file mode 100644 index e3cb995..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-2048px.png deleted file mode 100644 index 1dd2e5c..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-48px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-48px.png deleted file mode 100644 index 87b0130..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-600px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-600px.png deleted file mode 100644 index aeb5e2e..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-black-1024px.png deleted file mode 100644 index e639214..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-192px.png b/assets/eip-777/logo/png/ERC-777-logo-black-192px.png deleted file mode 100644 index 4130b57..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-black-2048px.png deleted file mode 100644 index 2485d96..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-48px.png b/assets/eip-777/logo/png/ERC-777-logo-black-48px.png deleted file mode 100644 index 1e76fda..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-600px.png b/assets/eip-777/logo/png/ERC-777-logo-black-600px.png deleted file mode 100644 index 07dc6e6..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-1024px.png deleted file mode 100644 index 961eb37..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-192px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-192px.png deleted file mode 100644 index 1f56130..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-2048px.png deleted file mode 100644 index 016df22..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png deleted file mode 100644 index 6bfe87c..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-600px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-600px.png deleted file mode 100644 index 70afecf..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-1024px.png deleted file mode 100644 index c678ace..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-192px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-192px.png deleted file mode 100644 index e7cd059..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-2048px.png deleted file mode 100644 index ccb78fc..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png deleted file mode 100644 index 870e21d..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-600px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-600px.png deleted file mode 100644 index 074d0a0..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-white-1024px.png deleted file mode 100644 index b2bdcbd..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-192px.png b/assets/eip-777/logo/png/ERC-777-logo-white-192px.png deleted file mode 100644 index f253d5c..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-white-2048px.png deleted file mode 100644 index be68567..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-48px.png b/assets/eip-777/logo/png/ERC-777-logo-white-48px.png deleted file mode 100644 index 32c3434..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-600px.png b/assets/eip-777/logo/png/ERC-777-logo-white-600px.png deleted file mode 100644 index b6157f5..0000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/svg/ERC-777-logo-beige.svg b/assets/eip-777/logo/svg/ERC-777-logo-beige.svg deleted file mode 100644 index 6af82af..0000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-beige.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-black.svg b/assets/eip-777/logo/svg/ERC-777-logo-black.svg deleted file mode 100644 index 53869e2..0000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-black.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-dark_grey.svg b/assets/eip-777/logo/svg/ERC-777-logo-dark_grey.svg deleted file mode 100644 index 9821736..0000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-dark_grey.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-light_grey.svg b/assets/eip-777/logo/svg/ERC-777-logo-light_grey.svg deleted file mode 100644 index 1e29b86..0000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-light_grey.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-white.svg b/assets/eip-777/logo/svg/ERC-777-logo-white.svg deleted file mode 100644 index fae203b..0000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-white.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-1.png b/assets/eip-823/eip-823-token-exchange-standard-visual-representation-1.png deleted file mode 100644 index 97fe0c1..0000000 Binary files a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-1.png and /dev/null differ diff --git a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-2.png b/assets/eip-823/eip-823-token-exchange-standard-visual-representation-2.png deleted file mode 100644 index bfaa387..0000000 Binary files a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-2.png and /dev/null differ diff --git a/assets/eip-858/calculations.md b/assets/eip-858/calculations.md deleted file mode 100644 index e02b94f..0000000 --- a/assets/eip-858/calculations.md +++ /dev/null @@ -1,24 +0,0 @@ -| Variable | Symbol | Value | Unit | Source | -| -------------------|--------------|---------------|---------------|--------| -| Network Hashrate |HN | 296000 | GH/s | https://etherscan.io/chart/hashrate | -| GPU Hashrate |HM | 31.2 | MH/s | https://www.legitreviews.com/geforce-gtx-1070-ethereum-mining-small-tweaks-great-hashrate-low-power_195451 | -| GPU Power |PM | 110.6 | W | https://www.reddit.com/r/ethereum/comments/7vewys/10000_tons_co2_per_day_and_climbing_eip_858/dtrswyz/ | - - -## Network Power Consumption (PN) - -A baseline value for network power consumption can be found by multiplying the total network hashrate with a "best case" value for the power/hashrate ratio that a miner can achieve. - -> PN = HN x PM / HM -> -> PN = 296000 (GH/s) x 110.6 (W) x 1000 (MH/GH) / ( 31.2 (MH/s) x 10^6 (W/MW) ) -> -> PN = 1049 MW - -As a side note, people often confuse power (W) and energy (power x time, eg. Wh). For instance, assuming an average daily PNd of 1049 MW we can calculate that days Energy consumption by multiplying by the number of hours in a day. - -> ENd = PNd x Td -> -> ENd = 1049 (MW) x 24 (h/d) / 1000 (GW/MW) -> -> ENd = 19.7 GWh