package types

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"math/big"
	"path"
	"reflect"
	"testing"

	aggkitcommon "github.com/agglayer/aggkit/common"
	"github.com/agglayer/aggkit/db"
	"github.com/agglayer/aggkit/l1infotreesync"
	"github.com/agglayer/aggkit/l1infotreesync/migrations"
	"github.com/agglayer/aggkit/log"
	"github.com/agglayer/aggkit/tree"
	"github.com/agglayer/aggkit/tree/types"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/stretchr/testify/require"
)

const (
	expectedSignedCertificateEmptyMetadataJSON = `{"network_id":1,"height":1,"prev_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","new_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":null}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":null},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}]}`
	expectedSignedCertificateMetadataJSON      = `{"network_id":1,"height":1,"prev_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","new_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000000","bridge_exits":[{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":"010203"}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":null,"dest_network":0,"dest_address":"0x0000000000000000000000000000000000000000","amount":"1","metadata":null},"claim_data":null,"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":1}}]}`
	fullCertificateJSON                        = `{"network_id":1,"height":0,"prev_local_exit_root":"0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757","new_local_exit_root":"0x79011be874bf6f229d8473eb251aa932210bc3ab843a316492d5bc0e4b9e945b","bridge_exits":[{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":0,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"10000005400000000","metadata":null}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":1,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"20000005400000000","metadata":null},"claim_data":{"Mainnet":{"l1_leaf":{"l1_info_tree_index":3,"rer":"0x0000000000000000000000000000000000000000000000000000000000000000","mer":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","inner":{"global_exit_root":"0xefb4efc883a8d7ab7c414684a4f44fac0f522d5eef9144dbad85a6b7756d770d","block_hash":"0x9cec78aa63185df6ae5e4a021dfd1d00913f35de3600564765ed07db6f5f7cec","timestamp":1741872827}},"proof_ger_l1root":{"root":"0xbace23ebc8bf61447ae0787134d7520a069ca8bacd96b4897767c02420656483","proof":{"siblings":["0xf1eba9d07291b35fd0b5c7c59efd235e666f614ae998e8d64431067e34f51d2b","0xfe17fd8b2fc79ce1c10f24a9692c085bdb49ba249629b4f4afc0ee9e5a547754","0x2ae60d22c940f1eac62674981b21adb092537dad03b80917f9f373853a7b12e7","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_leaf_mer":{"root":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","proof":{"siblings":["0x7e5dddb55a966fa6ccd6d470bb326a4fcef563567d6897c45b7ed885de710757","0x4b274df9344e005bfd46536d791100a85234bef4fab0348d1b2ffc0e7a709d33","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}}}},"global_index":{"mainnet_flag":true,"rollup_index":0,"leaf_index":2}},{"bridge_exit":{"leaf_type":"Transfer","token_info":{"origin_network":0,"origin_token_address":"0x0000000000000000000000000000000000000000"},"dest_network":1,"dest_address":"0xbece3a31343c6019cde0d5a4df2af8df17ebcb0f","amount":"1234567","metadata":null},"claim_data":{"Rollup":{"l1_leaf":{"l1_info_tree_index":4,"rer":"0x33267c0646fee979e59af1cd62f9e46cd0917f62aba82658e1a92a50e1d7b4d1","mer":"0x34c7e5206c4c793171805029b5a3a5c6f2d3e5344731cd69912142dc083768bf","inner":{"global_exit_root":"0x6df4684b75569ffa9c0d352d1293c5d98950ecc1ea34226194842d10b14f47d0","block_hash":"0x61d1de4c3c0732397c009a6913fd51a1607334d6d959030cfb994f5c4708b7e9","timestamp":1741872911}},"proof_ger_l1root":{"root":"0xbace23ebc8bf61447ae0787134d7520a069ca8bacd96b4897767c02420656483","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0x8646aa878bf9bdec51df10f340b0b35126c0b06fc69c8448a1f3d75c649480c6","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_leaf_ler":{"root":"0x156ab7795d0bb31ed548c43f90e71b8f06f71e5776a5ba444f3f3cb0935b4647","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}},"proof_ler_rer":{"root":"0x33267c0646fee979e59af1cd62f9e46cd0917f62aba82658e1a92a50e1d7b4d1","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5","0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30","0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85","0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344","0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d","0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968","0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83","0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af","0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0","0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5","0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892","0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c","0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb","0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc","0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2","0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f","0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a","0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0","0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0","0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2","0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9","0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377","0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652","0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef","0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d","0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0","0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e","0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e","0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322","0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735","0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9"]}}}},"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":0}}],"aggchain_data":{"aggchain_data_signature":{"signature":"59e46153ce34143f3b9cc3786f2473d8aa80ba578f07102d59ad01cbf221f81b4a9d68f0edc5635df585c134486b465bd588cd7f76a4d7154ba81e299f8f7ed601"}}}`
)

func TestBridgeExit_Hash(t *testing.T) {
	t.Parallel()

	MetadaHash := common.HexToHash("0x1234")
	bridge := BridgeExit{
		TokenInfo: &TokenInfo{},
		Metadata:  MetadaHash[:],
	}
	require.Equal(t, "0xaa57e4bf430fe25ca5068f9e1a25e8aef15744905cdf7635e0d5a468bd26bb18",
		bridge.Hash().String(), "use the hashed metadata, instead of calculating hash")

	bridge.Metadata = []byte{}
	require.Equal(t, "0xe3e297278c7df4ae4f235be10155ac62c53b08e2a14ed09b7dd6b688952ee883",
		bridge.Hash().String(), "metadata is a hashed and it's empty,use it")

	bridge.Metadata = nil
	require.Equal(t, "0xe3e297278c7df4ae4f235be10155ac62c53b08e2a14ed09b7dd6b688952ee883",
		bridge.Hash().String(), "metadata is nil and it's empty,use it")
}

func TestBridgeExit_Validate(t *testing.T) {
	t.Run("bridgeExit bad leaftype", func(t *testing.T) {
		bridgeExit := &BridgeExit{
			LeafType: 5,
			TokenInfo: &TokenInfo{
				OriginNetwork:      0,
				OriginTokenAddress: common.HexToAddress("0x1234"),
			},
			DestinationNetwork: 1,
			DestinationAddress: common.HexToAddress("0x1234"),
		}
		require.ErrorContains(t, bridgeExit.Validate(), "bridgeExit leaf type 5 is invalid")
	})
	t.Run("bridgeExit amount negative", func(t *testing.T) {
		bridgeExit := &BridgeExit{
			LeafType: 1,
			TokenInfo: &TokenInfo{
				OriginNetwork:      0,
				OriginTokenAddress: common.HexToAddress("0x1234"),
			},
			DestinationNetwork: 1,
			DestinationAddress: common.HexToAddress("0x1234"),
			Amount:             big.NewInt(-100),
		}
		require.ErrorContains(t, bridgeExit.Validate(), "bridgeExit amount -100 is negative")
	})
}

func TestGenericError_Error(t *testing.T) {
	t.Parallel()

	err := GenericError{"test", "value"}
	require.Equal(t, "[Agglayer Error] test: value", err.Error())
}

func TestCertificateHeader_ID(t *testing.T) {
	t.Parallel()

	certificate := CertificateHeader{
		Height:        1,
		CertificateID: common.HexToHash("0x123"),
	}
	require.Equal(t, "1/0x0000000000000000000000000000000000000000000000000000000000000123", certificate.ID())

	var certNil *CertificateHeader
	require.Equal(t, "nil", certNil.ID())
}

func TestCertificateHeaderString(t *testing.T) {
	t.Parallel()

	certificate := CertificateHeader{
		Height:        1,
		CertificateID: common.HexToHash("0x123"),
	}
	require.Equal(t, "Height: 1, CertificateID: 0x0000000000000000000000000000000000000000000000000000000000000123, PreviousLocalExitRoot: nil, NewLocalExitRoot: 0x0000000000000000000000000000000000000000000000000000000000000000. Status: Pending. SettlementTxnHash: nil, Errors: []",
		certificate.String())

	var certNil *CertificateHeader
	require.Equal(t, "nil", certNil.String())
}

func TestCertificateMarshalJSON(t *testing.T) {
	t.Parallel()

	t.Run("MarshalJSON with empty proofs", func(t *testing.T) {
		t.Parallel()

		cert := &Certificate{
			NetworkID:         1,
			Height:            1,
			PrevLocalExitRoot: common.Hash{},
			NewLocalExitRoot:  common.Hash{},
			BridgeExits: []*BridgeExit{
				{
					LeafType:           LeafTypeAsset,
					DestinationAddress: common.Address{},
					Amount:             big.NewInt(1),
				},
			},
			ImportedBridgeExits: []*ImportedBridgeExit{
				{
					BridgeExit: &BridgeExit{
						LeafType:           LeafTypeAsset,
						DestinationAddress: common.Address{},
						Amount:             big.NewInt(1),
						Metadata:           []byte{},
					},
					ClaimData: nil,
					GlobalIndex: &GlobalIndex{
						MainnetFlag: false,
						RollupIndex: 1,
						LeafIndex:   1,
					},
				},
			},
			L1InfoTreeLeafCount: 0,
		}

		data, err := json.Marshal(cert)
		require.NoError(t, err)
		log.Info(string(data))
		require.Equal(t, expectedSignedCertificateEmptyMetadataJSON, string(data))

		cert.BridgeExits[0].Metadata = []byte{1, 2, 3}
		data, err = json.Marshal(cert)
		require.NoError(t, err)
		log.Info(string(data))
		require.Equal(t, expectedSignedCertificateMetadataJSON, string(data))
	})

	t.Run("MarshalJSON with proofs", func(t *testing.T) {
		t.Parallel()

		cert := &Certificate{
			NetworkID:         11,
			Height:            111,
			PrevLocalExitRoot: common.HexToHash("0x111"),
			NewLocalExitRoot:  common.HexToHash("0x222"),
			BridgeExits: []*BridgeExit{
				{
					LeafType:           LeafTypeAsset,
					TokenInfo:          &TokenInfo{OriginNetwork: 1, OriginTokenAddress: common.HexToAddress("0x123")},
					DestinationNetwork: 2,
					DestinationAddress: common.HexToAddress("0x456"),
					Amount:             big.NewInt(1000),
					Metadata:           []byte{}, // we leave it empty on purpose to see when marshaled it will be null
				},
			},
			ImportedBridgeExits: []*ImportedBridgeExit{
				{
					BridgeExit: &BridgeExit{
						LeafType:           LeafTypeMessage,
						TokenInfo:          &TokenInfo{OriginNetwork: 1, OriginTokenAddress: common.HexToAddress("0x789")},
						DestinationNetwork: 2,
						DestinationAddress: common.HexToAddress("0xabc"),
						Amount:             big.NewInt(2000),
						Metadata:           []byte{0x03, 0x04},
					},
					GlobalIndex: &GlobalIndex{
						MainnetFlag: true,
						RollupIndex: 0,
						LeafIndex:   1,
					},
					ClaimData: &ClaimFromMainnet{
						ProofLeafMER: &MerkleProof{
							Root:  common.HexToHash("0x333"),
							Proof: createDummyProof(t),
						},
						ProofGERToL1Root: &MerkleProof{
							Root:  common.HexToHash("0x444"),
							Proof: createDummyProof(t),
						},
						L1Leaf: &L1InfoTreeLeaf{
							L1InfoTreeIndex: 1,
							RollupExitRoot:  common.HexToHash("0x555"),
							MainnetExitRoot: common.HexToHash("0x123456"),
							Inner: &L1InfoTreeLeafInner{
								GlobalExitRoot: common.HexToHash("0x777"),
								BlockHash:      common.HexToHash("0x888"),
								Timestamp:      12345678,
							},
						},
					},
				},
				{
					BridgeExit: &BridgeExit{
						LeafType:           LeafTypeAsset,
						TokenInfo:          &TokenInfo{OriginNetwork: 1, OriginTokenAddress: common.HexToAddress("0x789")},
						DestinationNetwork: 2,
						DestinationAddress: common.HexToAddress("0xabcdef"),
						Amount:             big.NewInt(2201),
						Metadata:           []byte{0x05, 0x08},
					},
					GlobalIndex: &GlobalIndex{
						MainnetFlag: false,
						RollupIndex: 1,
						LeafIndex:   2,
					},
					ClaimData: &ClaimFromRollup{
						ProofLeafLER: &MerkleProof{
							Root:  common.HexToHash("0x333"),
							Proof: createDummyProof(t),
						},
						ProofLERToRER: &MerkleProof{
							Root:  common.HexToHash("0x444"),
							Proof: createDummyProof(t),
						},
						ProofGERToL1Root: &MerkleProof{
							Root:  common.HexToHash("0x555"),
							Proof: createDummyProof(t),
						},
						L1Leaf: &L1InfoTreeLeaf{
							L1InfoTreeIndex: 2,
							RollupExitRoot:  common.HexToHash("0x532"),
							MainnetExitRoot: common.HexToHash("0x654321"),
							Inner: &L1InfoTreeLeafInner{
								GlobalExitRoot: common.HexToHash("0x777"),
								BlockHash:      common.HexToHash("0x888"),
								Timestamp:      12345678,
							},
						},
					},
				},
			},
		}

		expectedJSON := `{"network_id":11,"height":111,"prev_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000111","new_local_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000222","bridge_exits":[{"leaf_type":"Transfer","token_info":{"origin_network":1,"origin_token_address":"0x0000000000000000000000000000000000000123"},"dest_network":2,"dest_address":"0x0000000000000000000000000000000000000456","amount":"1000","metadata":null}],"imported_bridge_exits":[{"bridge_exit":{"leaf_type":"Message","token_info":{"origin_network":1,"origin_token_address":"0x0000000000000000000000000000000000000789"},"dest_network":2,"dest_address":"0x0000000000000000000000000000000000000abc","amount":"2000","metadata":"0304"},"claim_data":{"Mainnet":{"l1_leaf":{"l1_info_tree_index":1,"rer":"0x0000000000000000000000000000000000000000000000000000000000000555","mer":"0x0000000000000000000000000000000000000000000000000000000000123456","inner":{"global_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000777","block_hash":"0x0000000000000000000000000000000000000000000000000000000000000888","timestamp":12345678}},"proof_ger_l1root":{"root":"0x0000000000000000000000000000000000000000000000000000000000000444","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000009","0x000000000000000000000000000000000000000000000000000000000000000a","0x000000000000000000000000000000000000000000000000000000000000000b","0x000000000000000000000000000000000000000000000000000000000000000c","0x000000000000000000000000000000000000000000000000000000000000000d","0x000000000000000000000000000000000000000000000000000000000000000e","0x000000000000000000000000000000000000000000000000000000000000000f","0x0000000000000000000000000000000000000000000000000000000000000010","0x0000000000000000000000000000000000000000000000000000000000000011","0x0000000000000000000000000000000000000000000000000000000000000012","0x0000000000000000000000000000000000000000000000000000000000000013","0x0000000000000000000000000000000000000000000000000000000000000014","0x0000000000000000000000000000000000000000000000000000000000000015","0x0000000000000000000000000000000000000000000000000000000000000016","0x0000000000000000000000000000000000000000000000000000000000000017","0x0000000000000000000000000000000000000000000000000000000000000018","0x0000000000000000000000000000000000000000000000000000000000000019","0x000000000000000000000000000000000000000000000000000000000000001a","0x000000000000000000000000000000000000000000000000000000000000001b","0x000000000000000000000000000000000000000000000000000000000000001c","0x000000000000000000000000000000000000000000000000000000000000001d","0x000000000000000000000000000000000000000000000000000000000000001e","0x000000000000000000000000000000000000000000000000000000000000001f"]}},"proof_leaf_mer":{"root":"0x0000000000000000000000000000000000000000000000000000000000000333","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000009","0x000000000000000000000000000000000000000000000000000000000000000a","0x000000000000000000000000000000000000000000000000000000000000000b","0x000000000000000000000000000000000000000000000000000000000000000c","0x000000000000000000000000000000000000000000000000000000000000000d","0x000000000000000000000000000000000000000000000000000000000000000e","0x000000000000000000000000000000000000000000000000000000000000000f","0x0000000000000000000000000000000000000000000000000000000000000010","0x0000000000000000000000000000000000000000000000000000000000000011","0x0000000000000000000000000000000000000000000000000000000000000012","0x0000000000000000000000000000000000000000000000000000000000000013","0x0000000000000000000000000000000000000000000000000000000000000014","0x0000000000000000000000000000000000000000000000000000000000000015","0x0000000000000000000000000000000000000000000000000000000000000016","0x0000000000000000000000000000000000000000000000000000000000000017","0x0000000000000000000000000000000000000000000000000000000000000018","0x0000000000000000000000000000000000000000000000000000000000000019","0x000000000000000000000000000000000000000000000000000000000000001a","0x000000000000000000000000000000000000000000000000000000000000001b","0x000000000000000000000000000000000000000000000000000000000000001c","0x000000000000000000000000000000000000000000000000000000000000001d","0x000000000000000000000000000000000000000000000000000000000000001e","0x000000000000000000000000000000000000000000000000000000000000001f"]}}}},"global_index":{"mainnet_flag":true,"rollup_index":0,"leaf_index":1}},{"bridge_exit":{"leaf_type":"Transfer","token_info":{"origin_network":1,"origin_token_address":"0x0000000000000000000000000000000000000789"},"dest_network":2,"dest_address":"0x0000000000000000000000000000000000abcdef","amount":"2201","metadata":"0508"},"claim_data":{"Rollup":{"l1_leaf":{"l1_info_tree_index":2,"rer":"0x0000000000000000000000000000000000000000000000000000000000000532","mer":"0x0000000000000000000000000000000000000000000000000000000000654321","inner":{"global_exit_root":"0x0000000000000000000000000000000000000000000000000000000000000777","block_hash":"0x0000000000000000000000000000000000000000000000000000000000000888","timestamp":12345678}},"proof_ger_l1root":{"root":"0x0000000000000000000000000000000000000000000000000000000000000555","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000009","0x000000000000000000000000000000000000000000000000000000000000000a","0x000000000000000000000000000000000000000000000000000000000000000b","0x000000000000000000000000000000000000000000000000000000000000000c","0x000000000000000000000000000000000000000000000000000000000000000d","0x000000000000000000000000000000000000000000000000000000000000000e","0x000000000000000000000000000000000000000000000000000000000000000f","0x0000000000000000000000000000000000000000000000000000000000000010","0x0000000000000000000000000000000000000000000000000000000000000011","0x0000000000000000000000000000000000000000000000000000000000000012","0x0000000000000000000000000000000000000000000000000000000000000013","0x0000000000000000000000000000000000000000000000000000000000000014","0x0000000000000000000000000000000000000000000000000000000000000015","0x0000000000000000000000000000000000000000000000000000000000000016","0x0000000000000000000000000000000000000000000000000000000000000017","0x0000000000000000000000000000000000000000000000000000000000000018","0x0000000000000000000000000000000000000000000000000000000000000019","0x000000000000000000000000000000000000000000000000000000000000001a","0x000000000000000000000000000000000000000000000000000000000000001b","0x000000000000000000000000000000000000000000000000000000000000001c","0x000000000000000000000000000000000000000000000000000000000000001d","0x000000000000000000000000000000000000000000000000000000000000001e","0x000000000000000000000000000000000000000000000000000000000000001f"]}},"proof_leaf_ler":{"root":"0x0000000000000000000000000000000000000000000000000000000000000333","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000009","0x000000000000000000000000000000000000000000000000000000000000000a","0x000000000000000000000000000000000000000000000000000000000000000b","0x000000000000000000000000000000000000000000000000000000000000000c","0x000000000000000000000000000000000000000000000000000000000000000d","0x000000000000000000000000000000000000000000000000000000000000000e","0x000000000000000000000000000000000000000000000000000000000000000f","0x0000000000000000000000000000000000000000000000000000000000000010","0x0000000000000000000000000000000000000000000000000000000000000011","0x0000000000000000000000000000000000000000000000000000000000000012","0x0000000000000000000000000000000000000000000000000000000000000013","0x0000000000000000000000000000000000000000000000000000000000000014","0x0000000000000000000000000000000000000000000000000000000000000015","0x0000000000000000000000000000000000000000000000000000000000000016","0x0000000000000000000000000000000000000000000000000000000000000017","0x0000000000000000000000000000000000000000000000000000000000000018","0x0000000000000000000000000000000000000000000000000000000000000019","0x000000000000000000000000000000000000000000000000000000000000001a","0x000000000000000000000000000000000000000000000000000000000000001b","0x000000000000000000000000000000000000000000000000000000000000001c","0x000000000000000000000000000000000000000000000000000000000000001d","0x000000000000000000000000000000000000000000000000000000000000001e","0x000000000000000000000000000000000000000000000000000000000000001f"]}},"proof_ler_rer":{"root":"0x0000000000000000000000000000000000000000000000000000000000000444","proof":{"siblings":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000003","0x0000000000000000000000000000000000000000000000000000000000000004","0x0000000000000000000000000000000000000000000000000000000000000005","0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000009","0x000000000000000000000000000000000000000000000000000000000000000a","0x000000000000000000000000000000000000000000000000000000000000000b","0x000000000000000000000000000000000000000000000000000000000000000c","0x000000000000000000000000000000000000000000000000000000000000000d","0x000000000000000000000000000000000000000000000000000000000000000e","0x000000000000000000000000000000000000000000000000000000000000000f","0x0000000000000000000000000000000000000000000000000000000000000010","0x0000000000000000000000000000000000000000000000000000000000000011","0x0000000000000000000000000000000000000000000000000000000000000012","0x0000000000000000000000000000000000000000000000000000000000000013","0x0000000000000000000000000000000000000000000000000000000000000014","0x0000000000000000000000000000000000000000000000000000000000000015","0x0000000000000000000000000000000000000000000000000000000000000016","0x0000000000000000000000000000000000000000000000000000000000000017","0x0000000000000000000000000000000000000000000000000000000000000018","0x0000000000000000000000000000000000000000000000000000000000000019","0x000000000000000000000000000000000000000000000000000000000000001a","0x000000000000000000000000000000000000000000000000000000000000001b","0x000000000000000000000000000000000000000000000000000000000000001c","0x000000000000000000000000000000000000000000000000000000000000001d","0x000000000000000000000000000000000000000000000000000000000000001e","0x000000000000000000000000000000000000000000000000000000000000001f"]}}}},"global_index":{"mainnet_flag":false,"rollup_index":1,"leaf_index":2}}]}`

		data, err := json.Marshal(cert)
		require.NoError(t, err)
		require.Equal(t, expectedJSON, string(data))

		require.Equal(t, "0x4fb4c6fdd6a584be8369803d20fc19393d27f010398ae24ad32ba4744ee5e5d1", cert.CertificateID().String())
		require.Equal(t, "0x2f01782930cbf2bc2ab4ec16759a2288ad7df865dea387aadf55f96136269cf4", cert.BridgeExits[0].Hash().String())
		require.Equal(t, "0xe1a594db4275e6e5ab302057e48955c7faf53a8910497590a742b3da89046320", cert.ImportedBridgeExits[0].Hash().String())
		require.Equal(t, "0xcc9e20b86e9984d9f68b0252f224cb4bc774981c320ef375fb63706220f5af4d", cert.ImportedBridgeExits[1].Hash().String())
	})
}

func TestCertificate_ExtractAggchainParams(t *testing.T) {
	hash1 := common.HexToHash("0x1111")
	hash2 := common.HexToHash("0x2222")

	tests := []struct {
		name     string
		data     AggchainData
		expected []byte
	}{
		{
			name: "AggchainDataProof returns its params",
			data: &AggchainDataProof{
				AggchainParams: hash1,
			},
			expected: hash1.Bytes(),
		},
		{
			name: "AggchainDataMultisigWithProof returns nested params",
			data: &AggchainDataMultisigWithProof{
				AggchainProof: &AggchainDataProof{
					AggchainParams: hash2,
				},
			},
			expected: hash2.Bytes(),
		},
		{
			name: "AggchainDataMultisigWithProof with nil proof returns ZeroHash",
			data: &AggchainDataMultisigWithProof{
				AggchainProof: nil,
			},
			expected: aggkitcommon.ZeroHash.Bytes(),
		},
		{
			name:     "Nil AggchainData returns ZeroHash",
			data:     nil,
			expected: aggkitcommon.ZeroHash.Bytes(),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cert := &Certificate{
				AggchainData: tt.data,
			}
			actual := cert.ExtractAggchainParams()
			require.Equal(t, tt.expected, actual)
		})
	}
}

func TestGlobalIndex_UnmarshalFromMap(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name    string
		data    map[string]interface{}
		want    *GlobalIndex
		wantErr bool
	}{
		{
			name: "valid data",
			data: map[string]interface{}{
				"rollup_index": uint32(0),
				"leaf_index":   uint32(2),
				"mainnet_flag": true,
			},
			want: &GlobalIndex{
				RollupIndex: 0,
				LeafIndex:   2,
				MainnetFlag: true,
			},
			wantErr: false,
		},
		{
			name: "missing rollup_index",
			data: map[string]interface{}{
				"leaf_index":   uint32(2),
				"mainnet_flag": true,
			},
			want:    &GlobalIndex{},
			wantErr: true,
		},
		{
			name: "invalid rollup_index type",
			data: map[string]interface{}{
				"rollup_index": "invalid",
				"leaf_index":   uint32(2),
				"mainnet_flag": true,
			},
			want:    &GlobalIndex{},
			wantErr: true,
		},
		{
			name: "missing leaf_index",
			data: map[string]interface{}{
				"rollup_index": uint32(1),
				"mainnet_flag": true,
			},
			want:    &GlobalIndex{},
			wantErr: true,
		},
		{
			name: "invalid leaf_index type",
			data: map[string]interface{}{
				"rollup_index": uint32(1),
				"leaf_index":   "invalid",
				"mainnet_flag": true,
			},
			want:    &GlobalIndex{},
			wantErr: true,
		},
		{
			name: "missing mainnet_flag",
			data: map[string]interface{}{
				"rollup_index": uint32(1),
				"leaf_index":   uint32(2),
			},
			want:    &GlobalIndex{},
			wantErr: true,
		},
		{
			name: "invalid mainnet_flag type",
			data: map[string]interface{}{
				"rollup_index": uint32(1),
				"leaf_index":   uint32(2),
				"mainnet_flag": "invalid",
			},
			want:    &GlobalIndex{},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			g := &GlobalIndex{}
			err := g.UnmarshalFromMap(tt.data)
			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				require.Equal(t, tt.want, g)
			}
		})
	}
}

func TestUnmarshalCertificateHeader_UnknownError(t *testing.T) {
	t.Parallel()

	rawCertificateHeader := `{
		"network_id": 14,
		"height": 0,
		"epoch_number": null,
		"certificate_index": null,
		"certificate_id": "0x3af88c9ca106822bd141fdc680dcb888f4e9d4997fad1645ba3d5d747059eb32",
		"new_local_exit_root": "0x625e889ced3c31277c6653229096374d396a2fd3564a8894aaad2ff935d2fc8c",
		"metadata": "0x0000000000000000000000000000000000000000000000000000000000002f3d",
		"status": {
			"InError": {
				"error": {
					"ProofVerificationFailed": {
						"Plonk": "the verifying key does not match the inner plonk bn254 proof's committed verifying key"
					}
				}
			}
		}
	}`

	var result *CertificateHeader
	err := json.Unmarshal([]byte(rawCertificateHeader), &result)
	require.NoError(t, err)
	require.NotNil(t, result)

	expectedErr := &GenericError{
		Key:   "ProofVerificationFailed",
		Value: "{\"Plonk\":\"the verifying key does not match the inner plonk bn254 proof's committed verifying key\"}",
	}

	require.Equal(t, expectedErr, result.Error)
}

func TestConvertNumeric(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name        string
		value       float64
		target      reflect.Type
		expected    interface{}
		expectedErr error
	}{
		// Integer conversions
		{"FloatToInt", 42.5, reflect.TypeOf(int(0)), int(42), nil},
		{"FloatToInt8", 127.5, reflect.TypeOf(int8(0)), int8(127), nil},
		{"FloatToInt16", 32767.5, reflect.TypeOf(int16(0)), int16(32767), nil},
		{"FloatToInt32", 2147483647.5, reflect.TypeOf(int32(0)), int32(2147483647), nil},
		{"FloatToInt64", -10000000000000000.9, reflect.TypeOf(int64(0)), int64(-10000000000000000), nil},

		// Unsigned integer conversions
		{"FloatToUint", 42.5, reflect.TypeOf(uint(0)), uint(42), nil},
		{"FloatToUint8", 255.5, reflect.TypeOf(uint8(0)), uint8(255), nil},
		{"FloatToUint16", 65535.5, reflect.TypeOf(uint16(0)), uint16(65535), nil},
		{"FloatToUint32", 4294967295.5, reflect.TypeOf(uint32(0)), uint32(4294967295), nil},
		{"FloatToUint64", 10000000000000000.9, reflect.TypeOf(uint64(0)), uint64(10000000000000000), nil},

		// Float conversions
		{"FloatToFloat32", 3.14, reflect.TypeOf(float32(0)), float32(3.14), nil},
		{"FloatToFloat64", 3.14, reflect.TypeOf(float64(0)), float64(3.14), nil},

		// Unsupported type
		{"UnsupportedType", 3.14, reflect.TypeOf("string"), nil, errors.New("unsupported target type string")},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			result, err := convertNumeric(tt.value, tt.target)
			if tt.expectedErr != nil {
				require.ErrorContains(t, err, tt.expectedErr.Error())
			} else {
				require.NoError(t, err)
			}
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestCertificate_Hash(t *testing.T) {
	t.Parallel()

	// Test inputs
	prevLocalExitRoot := [common.HashLength]byte{}
	newLocalExitRoot := [common.HashLength]byte{}
	copy(prevLocalExitRoot[:], bytes.Repeat([]byte{0x01}, common.HashLength))
	copy(newLocalExitRoot[:], bytes.Repeat([]byte{0x02}, common.HashLength))

	// Create dummy BridgeExits
	bridgeExits := []*BridgeExit{
		{
			LeafType:           LeafTypeAsset,
			TokenInfo:          createDummyTokenInfo(t),
			DestinationNetwork: 1,
			DestinationAddress: common.HexToAddress("0x0000000000000000000000000000000000000001"),
			Amount:             big.NewInt(100),
			Metadata:           []byte("metadata1"),
		},
		{
			LeafType:           LeafTypeMessage,
			TokenInfo:          createDummyTokenInfo(t),
			DestinationNetwork: 2,
			DestinationAddress: common.HexToAddress("0x0000000000000000000000000000000000000002"),
			Amount:             big.NewInt(200),
			Metadata:           []byte("metadata2"),
		},
	}

	// Create dummy ImportedBridgeExits
	importedBridgeExits := []*ImportedBridgeExit{
		{
			BridgeExit: &BridgeExit{
				LeafType:           LeafTypeAsset,
				TokenInfo:          createDummyTokenInfo(t),
				DestinationNetwork: 3,
				DestinationAddress: common.HexToAddress("0x0000000000000000000000000000000000000003"),
				Amount:             big.NewInt(300),
				Metadata:           []byte("metadata3"),
			},
			ClaimData:   createDummyClaim(t),
			GlobalIndex: createDummyGlobalIndex(t),
		},
		{
			BridgeExit: &BridgeExit{
				LeafType:           LeafTypeAsset,
				TokenInfo:          createDummyTokenInfo(t),
				DestinationNetwork: 4,
				DestinationAddress: common.HexToAddress("0x0000000000000000000000000000000000000004"),
				Amount:             big.NewInt(400),
				Metadata:           []byte("metadata4"),
			},
			ClaimData:   createDummyClaim(t),
			GlobalIndex: createDummyGlobalIndex(t),
		},
	}

	// Create the certificate
	certificate := &Certificate{
		NetworkID:           1,
		Height:              100,
		PrevLocalExitRoot:   prevLocalExitRoot,
		NewLocalExitRoot:    newLocalExitRoot,
		BridgeExits:         bridgeExits,
		ImportedBridgeExits: importedBridgeExits,
	}

	// Manually calculate the expected hash
	bridgeExitsHashes := [][]byte{
		bridgeExits[0].Hash().Bytes(),
		bridgeExits[1].Hash().Bytes(),
	}
	importedBridgeExitsHashes := [][]byte{
		importedBridgeExits[0].Hash().Bytes(),
		importedBridgeExits[1].Hash().Bytes(),
	}

	bridgeExitsPart := crypto.Keccak256(bridgeExitsHashes...)
	importedBridgeExitsPart := crypto.Keccak256(importedBridgeExitsHashes...)

	expectedHash := crypto.Keccak256Hash(
		aggkitcommon.Uint32ToBytes(1),
		aggkitcommon.Uint64ToBigEndianBytes(100),
		prevLocalExitRoot[:],
		newLocalExitRoot[:],
		bridgeExitsPart,
		importedBridgeExitsPart,
		aggkitcommon.ZeroHash.Bytes(),
	)

	// Test the certificate hash
	calculatedHash := certificate.CertificateID()

	require.Equal(t, calculatedHash, expectedHash)
}

func TestClaimFromMainnnet_MarshalJSON(t *testing.T) {
	t.Parallel()

	// Test data
	merkleProof := &MerkleProof{
		Root: common.HexToHash("0x1"),
		Proof: [types.DefaultHeight]common.Hash{
			common.HexToHash("0x2"),
			common.HexToHash("0x3"),
		},
	}

	l1InfoTreeLeaf := &L1InfoTreeLeaf{
		L1InfoTreeIndex: 42,
		RollupExitRoot:  [common.HashLength]byte{0xaa, 0xbb, 0xcc},
		MainnetExitRoot: [common.HashLength]byte{0xdd, 0xee, 0xff},
		Inner: &L1InfoTreeLeafInner{
			GlobalExitRoot: common.HexToHash("0x1"),
			BlockHash:      common.HexToHash("0x2"),
			Timestamp:      1672531200, // Example timestamp
		},
	}

	claim := &ClaimFromMainnet{
		ProofLeafMER:     merkleProof,
		ProofGERToL1Root: merkleProof,
		L1Leaf:           l1InfoTreeLeaf,
	}

	// Marshal the ClaimFromMainnnet struct to JSON
	expectedJSON, err := claim.MarshalJSON()
	require.NoError(t, err)

	var actualClaim ClaimFromMainnet
	err = json.Unmarshal(expectedJSON, &actualClaim)
	require.NoError(t, err)
}

func TestBridgeExit_String(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name           string
		bridgeExit     *BridgeExit
		expectedOutput string
	}{
		{
			name: "With TokenInfo",
			bridgeExit: &BridgeExit{
				LeafType:           LeafTypeAsset,
				TokenInfo:          createDummyTokenInfo(t),
				DestinationNetwork: 100,
				DestinationAddress: common.HexToAddress("0x2"),
				Amount:             big.NewInt(1000),
				Metadata:           []byte{0x01, 0x02, 0x03},
			},
			expectedOutput: "LeafType: Transfer, DestinationNetwork: 100, DestinationAddress: 0x0000000000000000000000000000000000000002, Amount: 1000, Metadata: 010203, TokenInfo: OriginNetwork: 1, OriginTokenAddress: 0x0000000000000000000000000000000000002345",
		},
		{
			name: "Without TokenInfo",
			bridgeExit: &BridgeExit{
				LeafType:           LeafTypeMessage,
				DestinationNetwork: 200,
				DestinationAddress: common.HexToAddress("0x1"),
				Amount:             big.NewInt(5000),
				Metadata:           []byte{0xff, 0xee, 0xdd},
			},
			expectedOutput: "LeafType: Message, DestinationNetwork: 200, DestinationAddress: 0x0000000000000000000000000000000000000001, Amount: 5000, Metadata: ffeedd, TokenInfo: nil",
		},
		{
			name: "Without Amount",
			bridgeExit: &BridgeExit{
				LeafType:           LeafTypeMessage,
				DestinationNetwork: 200,
				DestinationAddress: common.HexToAddress("0x1"),
				Metadata:           []byte{0xff, 0xee, 0xdd},
			},
			expectedOutput: "LeafType: Message, DestinationNetwork: 200, DestinationAddress: 0x0000000000000000000000000000000000000001, Amount: <nil>, Metadata: ffeedd, TokenInfo: nil",
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			actualOutput := tt.bridgeExit.String()
			require.Equal(t, tt.expectedOutput, actualOutput)
		})
	}
}

func TestCertificateStatus_UnmarshalJSON(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name        string
		input       string
		expected    CertificateStatus
		expectError bool
	}{
		{
			name:        "Valid status - Pending",
			input:       `"Pending"`,
			expected:    Pending,
			expectError: false,
		},
		{
			name:        "Valid status - Proven",
			input:       `"Proven"`,
			expected:    Proven,
			expectError: false,
		},
		{
			name:        "Valid status - Candidate",
			input:       `"Candidate"`,
			expected:    Candidate,
			expectError: false,
		},
		{
			name:        "Valid status - InError",
			input:       `"InError"`,
			expected:    InError,
			expectError: false,
		},
		{
			name:        "Valid status - Settled",
			input:       `"Settled"`,
			expected:    Settled,
			expectError: false,
		},
		{
			name:        "Invalid status",
			input:       `"InvalidStatus"`,
			expected:    0, // Unchanged (default value of CertificateStatus)
			expectError: true,
		},
		{
			name:        "Contains 'InError' string",
			input:       `"SomeStringWithInError"`,
			expected:    InError,
			expectError: false,
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			var status CertificateStatus
			err := json.Unmarshal([]byte(tt.input), &status)

			if tt.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				require.Equal(t, tt.expected, status)
			}
		})
	}
}

func TestMerkleProof_String(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		proof    MerkleProof
		expected string
	}{
		{
			name: "Empty Root and Empty Proof",
			proof: MerkleProof{
				Root:  common.Hash{},
				Proof: [types.DefaultHeight]common.Hash{},
			},
			expected: fmt.Sprintf("Root: %s, Proof: %v", common.Hash{}.String(), [types.DefaultHeight]common.Hash{}),
		},
		{
			name: "Non-Empty Root and Empty Proof",
			proof: MerkleProof{
				Root:  common.HexToHash("0xabc123"),
				Proof: [types.DefaultHeight]common.Hash{},
			},
			expected: fmt.Sprintf("Root: %s, Proof: %v", common.HexToHash("0xabc123").String(), [types.DefaultHeight]common.Hash{}),
		},
		{
			name: "Non-Empty Root and Partially Populated Proof",
			proof: MerkleProof{
				Root: common.HexToHash("0xabc123"),
				Proof: [types.DefaultHeight]common.Hash{
					common.HexToHash("0xdef456"),
					common.HexToHash("0x123789"),
				},
			},
			expected: fmt.Sprintf("Root: %s, Proof: %v",
				common.HexToHash("0xabc123").String(),
				[types.DefaultHeight]common.Hash{
					common.HexToHash("0xdef456"),
					common.HexToHash("0x123789"),
				}),
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			result := tt.proof.String()
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestGlobalIndexString(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    GlobalIndex
		expected string
	}{
		{
			name: "All fields zero",
			input: GlobalIndex{
				MainnetFlag: false,
				RollupIndex: 0,
				LeafIndex:   0,
			},
			expected: "MainnetFlag: false, RollupIndex: 0, LeafIndex: 0",
		},
		{
			name: "MainnetFlag true, non-zero indices",
			input: GlobalIndex{
				MainnetFlag: true,
				RollupIndex: 123,
				LeafIndex:   456,
			},
			expected: "MainnetFlag: true, RollupIndex: 123, LeafIndex: 456",
		},
		{
			name: "MainnetFlag false, large indices",
			input: GlobalIndex{
				MainnetFlag: false,
				RollupIndex: 4294967295, // Maximum value of uint32
				LeafIndex:   2147483647, // Large but within uint32 range
			},
			expected: "MainnetFlag: false, RollupIndex: 4294967295, LeafIndex: 2147483647",
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			result := tt.input.String()
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestL1InfoTreeLeafString(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    L1InfoTreeLeaf
		expected string
	}{
		{
			name: "With valid Inner",
			input: L1InfoTreeLeaf{
				L1InfoTreeIndex: 1,
				RollupExitRoot:  common.HexToHash("0x01"),
				MainnetExitRoot: common.HexToHash("0x02"),
				Inner: &L1InfoTreeLeafInner{
					GlobalExitRoot: common.HexToHash("0x03"),
					BlockHash:      common.HexToHash("0x04"),
					Timestamp:      1234567890,
				},
			},
			expected: "L1InfoTreeIndex: 1, RollupExitRoot: 0x0000000000000000000000000000000000000000000000000000000000000001, " +
				"MainnetExitRoot: 0x0000000000000000000000000000000000000000000000000000000000000002, " +
				"Inner: GlobalExitRoot: 0x0000000000000000000000000000000000000000000000000000000000000003, " +
				"BlockHash: 0x0000000000000000000000000000000000000000000000000000000000000004, Timestamp: 1234567890",
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			result := tt.input.String()
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestClaimType(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name         string
		claim        Claim
		expectedType string
	}{
		{
			name:         "Mainnet claim",
			claim:        &ClaimFromMainnet{},
			expectedType: "Mainnet",
		},
		{
			name:         "Rollup claim",
			claim:        &ClaimFromRollup{},
			expectedType: "Rollup",
		},
	}

	for _, c := range cases {
		c := c

		t.Run(c.name, func(t *testing.T) {
			t.Parallel()

			actualType := c.claim.Type()
			require.Equal(t, c.expectedType, actualType)
		})
	}
}

func Test_ProblematicBridgeExitHash(t *testing.T) {
	bridgeExit := &BridgeExit{
		LeafType: LeafTypeAsset,
		TokenInfo: &TokenInfo{
			OriginNetwork:      0,
			OriginTokenAddress: common.HexToAddress("0x0000000000000000000000000000000000000000"),
		},
		DestinationNetwork: 1,
		DestinationAddress: common.HexToAddress("0xc949254d682d8c9ad5682521675b8f43b102aec4"),
		Amount:             new(big.Int).SetUint64(10000000000000000000),
	}

	require.Equal(t, "0x22ed288677b4c2afd83a6d7d55f7df7f4eaaf60f7310210c030fd27adacbc5e0", bridgeExit.Hash().Hex())
}

func Test_UnmarshalCertificate(t *testing.T) {
	var cert Certificate
	err := json.Unmarshal([]byte(fullCertificateJSON), &cert)
	require.NoError(t, err)
	marshalData, err := json.Marshal(cert)
	require.NoError(t, err)
	require.JSONEq(t, fullCertificateJSON, string(marshalData))
}

func Test_UnmarshalImportedBridgeExit(t *testing.T) {
	cases := []struct {
		name           string
		importedBridge ImportedBridgeExit
	}{
		{
			name:           "Empty",
			importedBridge: ImportedBridgeExit{},
		},
		{
			name: "Empty/BridgeExit",
			importedBridge: ImportedBridgeExit{
				BridgeExit: &BridgeExit{},
			},
		},
		{
			name: "Empty/GlobalIndex",
			importedBridge: ImportedBridgeExit{
				GlobalIndex: &GlobalIndex{},
			},
		},
		{
			name: "Empty/ClaimFromRollup",
			importedBridge: ImportedBridgeExit{
				GlobalIndex: &GlobalIndex{},
				ClaimData:   &ClaimFromRollup{},
			},
		},
		{
			name: "Empty/ClaimFromMainnnet",
			importedBridge: ImportedBridgeExit{
				ClaimData: &ClaimFromMainnet{
					ProofLeafMER: &MerkleProof{
						Root:  common.HexToHash("0x1"),
						Proof: [types.DefaultHeight]common.Hash{common.HexToHash("0x2"), common.HexToHash("0x3")},
					},
					ProofGERToL1Root: &MerkleProof{},
					L1Leaf:           &L1InfoTreeLeaf{},
				},
			},
		},

		{
			name: "Mainnet claim",
			importedBridge: ImportedBridgeExit{
				BridgeExit: &BridgeExit{
					LeafType: LeafTypeAsset,
					TokenInfo: &TokenInfo{
						OriginNetwork:      0,
						OriginTokenAddress: common.HexToAddress("0x1234"),
					},
					DestinationNetwork: 1,
					DestinationAddress: common.HexToAddress("0x1234"),
					Amount:             big.NewInt(1000),
					Metadata:           []byte{0x01, 0x02, 0x03},
				},
				ClaimData: &ClaimFromMainnet{},
				GlobalIndex: &GlobalIndex{
					MainnetFlag: true,
					RollupIndex: 1,
					LeafIndex:   2,
				},
			},
		},
	}
	for _, tt := range cases {
		t.Run(tt.name, func(t *testing.T) {
			ser, err := json.Marshal(tt.importedBridge)
			require.NoError(t, err)
			unmarshallBridge := &ImportedBridgeExit{}
			err = json.Unmarshal(ser, unmarshallBridge)
			require.NoError(t, err)
			require.Equal(t, tt.importedBridge, *unmarshallBridge)
		})
	}
}

func Test_UnmarshalMerkleProof(t *testing.T) {
	mp := &MerkleProof{
		Root:  common.HexToHash("0x1"),
		Proof: [types.DefaultHeight]common.Hash{common.HexToHash("0x2"), common.HexToHash("0x3")},
	}
	ser, err := json.Marshal(mp)
	require.NoError(t, err)
	unmarshallMp := &MerkleProof{}
	err = json.Unmarshal(ser, unmarshallMp)
	require.NoError(t, err)
	require.Equal(t, mp, unmarshallMp)
}

func Test_UnmarshalL1InfoTreeLeaf(t *testing.T) {
	data := L1InfoTreeLeaf{
		L1InfoTreeIndex: 1,
		RollupExitRoot:  [common.HashLength]byte{0xaa, 0xbb, 0xcc},
		MainnetExitRoot: [common.HashLength]byte{0xdd, 0xee, 0xff},
		Inner: &L1InfoTreeLeafInner{
			GlobalExitRoot: common.HexToHash("0x1"),
			BlockHash:      common.HexToHash("0x2"),
			Timestamp:      1672531200, // Example timestamp
		},
	}
	ser, err := json.Marshal(data)
	require.NoError(t, err)
	unmarshalled := &L1InfoTreeLeaf{}
	err = json.Unmarshal(ser, unmarshalled)
	require.NoError(t, err)
	require.Equal(t, data, *unmarshalled)
}

func Test_UnmarshalBridgeExit(t *testing.T) {
	cases := []struct {
		name string
		data *BridgeExit
	}{
		{
			name: "metadataHashed",
			data: &BridgeExit{
				LeafType: LeafTypeAsset,
				Metadata: []byte{0x01, 0x02, 0x03},
			},
		},
	}

	for _, tt := range cases {
		t.Run(tt.name, func(t *testing.T) {
			ser, err := json.Marshal(tt.data)
			require.NoError(t, err)
			unmarshalled := &BridgeExit{}
			err = json.Unmarshal(ser, unmarshalled)
			require.NoError(t, err)
			require.Equal(t, tt.data, unmarshalled)
		})
	}
}

func Test_UnmarshalClaimFromMainnnet(t *testing.T) {
	claim := &ClaimFromMainnet{
		ProofLeafMER: &MerkleProof{
			Root:  common.HexToHash("0x1"),
			Proof: [types.DefaultHeight]common.Hash{common.HexToHash("0x2"), common.HexToHash("0x3")},
		},
		ProofGERToL1Root: &MerkleProof{},
		L1Leaf:           &L1InfoTreeLeaf{},
	}
	ser, err := json.Marshal(claim)
	require.NoError(t, err)
	unmarshalled := &ClaimFromMainnet{}
	err = json.Unmarshal(ser, unmarshalled)
	require.NoError(t, err)
	require.Equal(t, claim, unmarshalled)
}

func Test_UnmarshalClaimFromRollup(t *testing.T) {
	claim := &ClaimFromRollup{
		ProofLeafLER: &MerkleProof{
			Root:  common.HexToHash("0x1"),
			Proof: [types.DefaultHeight]common.Hash{common.HexToHash("0x2"), common.HexToHash("0x3")},
		},
		ProofLERToRER: &MerkleProof{
			Root: common.HexToHash("0x4"),
		},
		ProofGERToL1Root: &MerkleProof{
			Root: common.HexToHash("0x5"),
		},

		L1Leaf: &L1InfoTreeLeaf{
			L1InfoTreeIndex: 1,
		},
	}
	ser, err := json.Marshal(claim)
	require.NoError(t, err)
	unmarshalled := &ClaimFromRollup{}
	err = json.Unmarshal(ser, unmarshalled)
	require.NoError(t, err)
	require.Equal(t, claim, unmarshalled)
}

func TestCertificate_ID(t *testing.T) {
	var cert *Certificate
	require.Equal(t, "cert{"+nilStr+"}", cert.ID())
	cert = &Certificate{
		NetworkID: 1,
		Height:    2,
	}
	require.Equal(t, "cert{height:2, networkID:1}", cert.ID())
}

func TestAggchainDataSignature_MarshalUnmarshalJSON(t *testing.T) {
	signature := &AggchainDataSignature{
		Signature: common.FromHex("0x1234567890abcdef"),
	}

	expectedJSON := `{"aggchain_data_signature":{"signature":"1234567890abcdef"}}`

	jsonData, err := signature.MarshalJSON()
	require.NoError(t, err)
	require.JSONEq(t, expectedJSON, string(jsonData))

	var unmarshalled AggchainDataSignature
	require.NoError(t, unmarshalled.UnmarshalJSON(jsonData))
	require.Equal(t, *signature, unmarshalled)
}

func TestAggchainDataProof_MarshalUnmarshalJSON(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    *AggchainDataProof
		expected string
	}{
		{
			name: "Valid AggchainDataProof",
			input: &AggchainDataProof{
				Proof:          common.FromHex("0x123456"),
				AggchainParams: common.HexToHash("0xabcdef"),
				Context:        map[string][]byte{},
				Version:        "0.1",
				Vkey:           common.FromHex("0x123456"),
				Signature:      []byte{0x01, 0x02, 0x03},
			},
			expected: `{"aggchain_data_proof":{"proof":"123456","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000abcdef","context":{},"version":"0.1","vkey":"123456","signature":"010203"}}`,
		},
		{
			name: "Empty AggchainDataProof, Context and Signature",
			input: &AggchainDataProof{
				Proof:          []byte{},
				AggchainParams: common.Hash{},
				Context:        map[string][]byte{},
				Signature:      []byte{},
				Version:        "",
				Vkey:           []byte{},
			},
			expected: `{"aggchain_data_proof":{"proof":"","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000000000","context":{},"version":"","vkey":"","signature":""}}`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			result, err := tt.input.MarshalJSON()
			require.NoError(t, err)
			require.JSONEq(t, tt.expected, string(result))

			var unmarshalled AggchainDataProof
			require.NoError(t, unmarshalled.UnmarshalJSON(result))
			require.Equal(t, *tt.input, unmarshalled)
		})
	}
}

func TestAggchainDataMultisig_MarshalUnmarshalJSON(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    *AggchainDataMultisig
		expected string
	}{
		{
			name: "Valid AggchainDataMultisig with signatures",
			input: &AggchainDataMultisig{
				Multisig: &Multisig{
					Signatures: []ECDSAMultisigEntry{
						{
							Index:     0,
							Signature: []byte{0x01, 0x02, 0x03},
						},
						{
							Index:     1,
							Signature: []byte{0x04, 0x05, 0x06},
						},
					},
				},
			},
			expected: `{"aggchain_data_multisig":{"multisig":{"signatures":[{"index":0,"signature":"010203"},{"index":1,"signature":"040506"}]}}}`,
		},
		{
			name: "Empty AggchainDataMultisig",
			input: &AggchainDataMultisig{
				Multisig: &Multisig{
					Signatures: []ECDSAMultisigEntry{},
				},
			},
			expected: `{"aggchain_data_multisig":{"multisig":{"signatures":[]}}}`,
		},
		{
			name: "Nil Multisig",
			input: &AggchainDataMultisig{
				Multisig: nil,
			},
			expected: `{"aggchain_data_multisig":{"multisig":null}}`,
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			result, err := tt.input.MarshalJSON()
			require.NoError(t, err)
			require.JSONEq(t, tt.expected, string(result))

			var unmarshalled AggchainDataMultisig
			require.NoError(t, unmarshalled.UnmarshalJSON(result))
			require.Equal(t, *tt.input, unmarshalled)
		})
	}
}

func TestAggchainDataMultisigWithProof_MarshalUnmarshalJSON(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    *AggchainDataMultisigWithProof
		expected string
	}{
		{
			name: "Valid AggchainDataMultisigWithProof with all fields",
			input: &AggchainDataMultisigWithProof{
				Multisig: &Multisig{
					Signatures: []ECDSAMultisigEntry{
						{
							Index:     0,
							Signature: []byte{0x01, 0x02, 0x03},
						},
					},
				},
				AggchainProof: &AggchainDataProof{
					Proof:          []byte{0xaa, 0xbb, 0xcc},
					AggchainParams: common.HexToHash("0x123456"),
					Context:        map[string][]byte{"key": {0xdd, 0xee}},
					Version:        "1.0",
					Vkey:           []byte{0xff},
					Signature:      []byte{0x11, 0x22},
				},
			},
			expected: `{"aggchain_data_multisig_with_proof":{"multisig":{"signatures":[{"index":0,"signature":"010203"}]},"aggchain_proof":{"aggchain_data_proof":{"proof":"aabbcc","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000123456","context":{"key":"3e4="},"version":"1.0","vkey":"ff","signature":"1122"}}}}`,
		},
		{
			name: "Empty multisig with proof",
			input: &AggchainDataMultisigWithProof{
				Multisig: &Multisig{
					Signatures: []ECDSAMultisigEntry{},
				},
				AggchainProof: &AggchainDataProof{
					Proof:          []byte{},
					AggchainParams: common.Hash{},
					Context:        map[string][]byte{},
					Version:        "",
					Vkey:           []byte{},
					Signature:      []byte{},
				},
			},
			expected: `{"aggchain_data_multisig_with_proof":{"multisig":{"signatures":[]},"aggchain_proof":{"aggchain_data_proof":{"proof":"","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000000000","context":{},"version":"","vkey":"","signature":""}}}}`,
		},
		{
			name: "Nil Multisig with valid proof",
			input: &AggchainDataMultisigWithProof{
				Multisig: nil,
				AggchainProof: &AggchainDataProof{
					Proof:     []byte{0x99},
					Version:   "2.0",
					Vkey:      []byte{0xff},
					Signature: []byte{0x01, 0x02},
				},
			},
			expected: `{"aggchain_data_multisig_with_proof":{"multisig":null,"aggchain_proof":{"aggchain_data_proof":{"proof":"99","aggchain_params":"0x0000000000000000000000000000000000000000000000000000000000000000","context":null,"version":"2.0","vkey":"ff","signature":"0102"}}}}`,
		},
		{
			name: "Valid multisig with nil proof",
			input: &AggchainDataMultisigWithProof{
				Multisig: &Multisig{
					Signatures: []ECDSAMultisigEntry{
						{
							Index:     2,
							Signature: []byte{0xaa},
						},
					},
				},
				AggchainProof: nil,
			},
			expected: `{"aggchain_data_multisig_with_proof":{"multisig":{"signatures":[{"index":2,"signature":"aa"}]},"aggchain_proof":null}}`,
		},
		{
			name: "Both nil",
			input: &AggchainDataMultisigWithProof{
				Multisig:      nil,
				AggchainProof: nil,
			},
			expected: `{"aggchain_data_multisig_with_proof":{"multisig":null,"aggchain_proof":null}}`,
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			result, err := tt.input.MarshalJSON()
			require.NoError(t, err)
			fmt.Println(string(result))
			require.JSONEq(t, tt.expected, string(result))

			var unmarshalled AggchainDataMultisigWithProof
			require.NoError(t, unmarshalled.UnmarshalJSON(result))
			require.Equal(t, *tt.input, unmarshalled)
		})
	}
}

func TestCertificate_Validate(t *testing.T) {
	t.Run("fails cert nil", func(t *testing.T) {
		var cert *Certificate
		require.Error(t, cert.Validate(), "should fail with nil certificate")
	})
	t.Run("fails bridgeExit nil", func(t *testing.T) {
		cert := Certificate{
			BridgeExits: []*BridgeExit{nil},
		}
		require.ErrorContains(t, cert.Validate(), "bridgeExit is nil")
	})
	t.Run("fails importedBridgeExit nil", func(t *testing.T) {
		cert := Certificate{
			ImportedBridgeExits: []*ImportedBridgeExit{nil},
		}
		require.ErrorContains(t, cert.Validate(), "ImportedBridgeExit is nil")
	})
	t.Run("fails globalIndex nil", func(t *testing.T) {
		cert := Certificate{
			ImportedBridgeExits: []*ImportedBridgeExit{
				{
					BridgeExit: &BridgeExit{
						LeafType: LeafTypeAsset,
						TokenInfo: &TokenInfo{
							OriginNetwork:      0,
							OriginTokenAddress: common.HexToAddress("0x1234"),
						},
						DestinationNetwork: 1,
						DestinationAddress: common.HexToAddress("0x1234"),
					},
					ClaimData: &ClaimFromMainnet{
						ProofLeafMER:     &MerkleProof{},
						ProofGERToL1Root: &MerkleProof{},
						L1Leaf: &L1InfoTreeLeaf{
							Inner: &L1InfoTreeLeafInner{},
						},
					},
				},
			},
		}
		require.ErrorContains(t, cert.Validate(), "globalIndex is nil")
	})

	t.Run("L1InfoTreeLeaf nil", func(t *testing.T) {
		var sut *L1InfoTreeLeaf
		require.ErrorContains(t, sut.Validate(), "L1InfoTreeLeaf is nil")
		sut = &L1InfoTreeLeaf{}
		require.ErrorContains(t, sut.Validate(), "L1InfoTreeLeaf inner is nil")
		sut = &L1InfoTreeLeaf{
			Inner: &L1InfoTreeLeafInner{},
		}
		require.NoError(t, sut.Validate())
	})

	t.Run("ImportedBridgeExit nil", func(t *testing.T) {
		var sut *ImportedBridgeExit
		require.ErrorContains(t, sut.Validate(), "ImportedBridgeExit is nil")
		sut = &ImportedBridgeExit{}
		require.ErrorContains(t, sut.Validate(), "ImportedBridgeExit.BridgeExit not valid")
	})
}

func TestBridgeExitHash(t *testing.T) {
	var sut *BridgeExit
	require.Equal(t, common.Hash{}, sut.Hash())
	var sut2 BridgeExit
	require.Equal(t, common.Hash{}, sut2.Hash())
}

func TestClaimFromMainnnetHash(t *testing.T) {
	var sut *ClaimFromMainnet
	require.Equal(t, common.Hash{}, sut.Hash())
	var sut2 ClaimFromMainnet
	require.Equal(t, common.Hash{}, sut2.Hash())
}

func TestClaimFromRollupHash(t *testing.T) {
	var sut *ClaimFromRollup
	require.Equal(t, common.Hash{}, sut.Hash())
	var sut2 ClaimFromRollup
	require.Equal(t, common.Hash{}, sut2.Hash())
}

func TestImportedBridgeExit_VerifyProofs(t *testing.T) {
	t.Parallel()

	ctx := t.Context()
	treeDB := createTreeDBForTest(t)
	rollupExitTree := tree.NewAppendOnlyTree(treeDB, migrations.RollupExitTreePrefix)
	l1InfoTree := tree.NewAppendOnlyTree(treeDB, migrations.L1InfoTreePrefix)

	numOfLeavesToAdd := 10

	tx, err := db.NewTx(ctx, treeDB)
	require.NoError(t, err)

	bridgeExits := make([]*BridgeExit, numOfLeavesToAdd)
	l1Leaves := make([]*L1InfoTreeLeaf, numOfLeavesToAdd)

	// simulate rollup exit tree
	for i := range numOfLeavesToAdd {
		bridgeExits[i] = &BridgeExit{
			LeafType: LeafTypeAsset,
			TokenInfo: &TokenInfo{
				OriginNetwork:      uint32(i),
				OriginTokenAddress: common.HexToAddress(fmt.Sprintf("0x%040x", i)),
			},
			DestinationNetwork: uint32(i + 1),
			DestinationAddress: common.HexToAddress(fmt.Sprintf("0x%040x", i+1)),
			Amount:             big.NewInt(int64(i+1) * 1000),
		}
		require.NoError(t, rollupExitTree.AddLeaf(tx, uint64(i), 0, types.Leaf{
			Index: uint32(i),
			Hash:  bridgeExits[i].Hash(),
		}))
	}

	require.NoError(t, tx.Commit())

	tx, err = db.NewTx(ctx, treeDB)
	require.NoError(t, err)

	// simulate L1 info tree
	for i := range numOfLeavesToAdd {
		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(i))
		require.NoError(t, err)

		l1Leaves[i] = &L1InfoTreeLeaf{
			L1InfoTreeIndex: uint32(i),
			RollupExitRoot:  rer.Hash,
			MainnetExitRoot: rer.Hash,
			Inner: &L1InfoTreeLeafInner{
				BlockHash:      common.HexToHash(fmt.Sprintf("0x%040x", i*4+1)),
				Timestamp:      uint64(i * 1000),
				GlobalExitRoot: l1infotreesync.CalculateGER(rer.Hash, rer.Hash),
			},
		}

		require.NoError(t, l1InfoTree.AddLeaf(tx, uint64(i), 0, types.Leaf{
			Index: uint32(i),
			Hash:  l1Leaves[i].Hash(),
		}))
	}

	require.NoError(t, tx.Commit())

	// lastRER, err := rollupExitTree.GetLastRoot(nil)
	// require.NoError(t, err)

	lastL1InfoRoot, err := l1InfoTree.GetLastRoot(nil)
	require.NoError(t, err)

	t.Run("ClaimFromMainnet - Valid Proofs", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromMainnet{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromL1InfoTree,
				},
				ProofLeafMER: &MerkleProof{
					Root:  l1Leaves[0].MainnetExitRoot,
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex: 0,
			},
		}

		require.NoError(t, ibe.VerifyProofs(lastL1InfoRoot.Hash))
	})

	t.Run("ClaimFromMainnet - Invalid Proofs - Wrong L1 Root", func(t *testing.T) {
		t.Parallel()

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromMainnet{
				ProofGERToL1Root: &MerkleProof{
					Root:  common.HexToHash("0x12345678"), // Wrong root
					Proof: proofFromL1InfoTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex: 0,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), " Mismatch between the provided L1 root and the inclusion proof")
	})

	t.Run("ClaimFromMainnet - Mismatch on MER", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromMainnet{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromRollupExitTree,
				},
				ProofLeafMER: &MerkleProof{
					Root:  common.HexToHash("0x12345678"), // Wrong root
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex: 0,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Mismatch on the MER between the L1 leaf and the inclusion proof")
	})

	t.Run("ClaimFromMainnet - Invalid merkle path from the leaf to LER", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)
		proofFromRollupExitTree[0][0] ^= 0xFF // Corrupt the proof

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromMainnet{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromL1InfoTree,
				},
				ProofLeafMER: &MerkleProof{
					Root:  l1Leaves[0].MainnetExitRoot,
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex: 0,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "nvalid merkle path from the leaf to the LER")
	})

	t.Run("ClaimFromMainnet - Invalid merkle path from the GER to the L1 Info Root", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		proofFromL1InfoTree[0][0] ^= 0xFF // Corrupt the proof

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromMainnet{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromL1InfoTree,
				},
				ProofLeafMER: &MerkleProof{
					Root:  l1Leaves[0].MainnetExitRoot,
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex: 0,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Invalid merkle path from the GER to the L1 Info Root")
	})

	t.Run("ClaimRollup - Invalid Proofs - Wrong L1 Root", func(t *testing.T) {
		t.Parallel()

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromRollup{
				ProofGERToL1Root: &MerkleProof{
					Root:  common.HexToHash("0x12345678"), // Wrong root
					Proof: proofFromL1InfoTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex: 0,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Mismatch between the provided L1 root and the inclusion proof")
	})

	t.Run("ClaimFromRollup - Mismatch on RER", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromRollup{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromRollupExitTree,
				},
				ProofLERToRER: &MerkleProof{
					Root:  common.HexToHash("0x12345678"), // Wrong root
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex: 0,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Mismatch on the RER between the L1 leaf and the inclusion proof")
	})

	t.Run("ClaimFromRollup - Invalid merkle path from the leaf to LER", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)
		proofFromRollupExitTree[0][0] ^= 0xFF // Corrupt the proof

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromRollup{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromL1InfoTree,
				},
				ProofLERToRER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				ProofLeafLER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex:   0,
				RollupIndex: 0,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Invalid merkle path from the leaf to the LER")
	})

	t.Run("ClaimFromRollup - Invalid merkle path from the LER to the RER", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)

		proofCopy := proofFromRollupExitTree
		proofCopy[0][0] ^= 0xFF // Corrupt the proof

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromRollup{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromL1InfoTree,
				},
				ProofLERToRER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				ProofLeafLER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex:   0,
				RollupIndex: 1,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Invalid merkle path from the LER to the RER")
	})

	t.Run("ClaimFromRollup - Invalid merkle path from the GER to the L1 Info Root", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)
		proofFromL1InfoTree[0][0] ^= 0xFF // Corrupt the proof

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromRollup{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromL1InfoTree,
				},
				ProofLERToRER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				ProofLeafLER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex:   0,
				RollupIndex: 1,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Invalid merkle path from the GER to the L1 Info Root")
	})

	t.Run("ClaimFromRollup - Invalid merkle path from the GER to the L1 Info Root", func(t *testing.T) {
		t.Parallel()

		rer, err := rollupExitTree.GetRootByIndex(ctx, uint32(0))
		require.NoError(t, err)
		proofFromRollupExitTree, err := rollupExitTree.GetProof(ctx, uint32(0), rer.Hash)
		require.NoError(t, err)

		proofFromL1InfoTree, err := l1InfoTree.GetProof(ctx, uint32(0), lastL1InfoRoot.Hash)
		require.NoError(t, err)

		ibe := &ImportedBridgeExit{
			BridgeExit: bridgeExits[0],
			ClaimData: &ClaimFromRollup{
				ProofGERToL1Root: &MerkleProof{
					Root:  lastL1InfoRoot.Hash,
					Proof: proofFromL1InfoTree,
				},
				ProofLERToRER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				ProofLeafLER: &MerkleProof{
					Root:  l1Leaves[0].RollupExitRoot,
					Proof: proofFromRollupExitTree,
				},
				L1Leaf: l1Leaves[0],
			},
			GlobalIndex: &GlobalIndex{
				LeafIndex:   0,
				RollupIndex: 1,
			},
		}

		require.ErrorContains(t, ibe.VerifyProofs(lastL1InfoRoot.Hash), "Invalid merkle path from the LER to the RER")
	})
}

func createTreeDBForTest(t *testing.T) *sql.DB {
	t.Helper()
	dbPath := path.Join(t.TempDir(), "tree_createTreeDBForTest.sqlite")
	err := migrations.RunMigrations(dbPath)
	require.NoError(t, err)
	treeDB, err := db.NewSQLiteDB(dbPath)
	require.NoError(t, err)
	return treeDB
}
