As of 3 Nov 2026, Elysium has 1 audit report on Trustblock, by CertiK, dated 13 Feb 2023. That report lists no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.
Newest first
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 1 |
| Medium | 0 | 0 | 1 |
| Low | 0 | 0 | 27 |
The bridge admin can freely add bridge relayers, which can cause significant centralization risk. The bridge operates through the admin controlling the top level of the bridge, like other admins and relayers, and the relayers processing the actual transfers. However, there is no filtering or governance process when adding new relayers, which could allow the admin to overtake the relaying process with many new relayers. ```go=76 func AddRelayerEVMCMD(cmd *cobra.Command, args []string, contract *bridge.BridgeContract) error { log.Debug().Msgf(` Adding relayer Relayer address: %s Bridge address: %s`, Relayer, Bridge) _, err := contract.AddRelayer(RelayerAddr, transactor.TransactOptions{GasLimit: gasLimit}) return err } ```
Open the reportThe error within `Execute` which handles the voting process is not properly handled. The function `Execute` will return an error if any internal calls or logics fails. Also, the relayer's `route' function will directly return without handling the error returned from `Execute`. This means that this relayer will miss the deposit event while it is running. At the next startup if config.StartBlock is changed to be larger than the failed block, the deposit will be unaccounted for. File: chains/evm/executor/voter.go ```go=109 func (v *EVMVoter) Execute(m *message.Message) error { ... } ``` File: relayer/relayer.go ```go=90 if err := destChain.Write(m); err != nil { log.Error().Err(err).Msgf("writing message %+v", m) return } ```
Open the reportEmpty or wrong destination address will cause the sender address to become the destination address in `ProcessMintFlags`. ```go=41 Args: func(cmd *cobra.Command, args []string) error { err := ValidateMintFlags(cmd, args) if err != nil { return err } err = ProcessMintFlags(cmd, args) return err }, ``` ```go=74 func ProcessMintFlags(cmd *cobra.Command, args []string) error { Erc721Addr = common.HexToAddress(Erc721Address) if !common.IsHexAddress(Dst) { DstAddress = senderKeyPair.CommonAddress() } else { DstAddress = common.HexToAddress(Dst) } ```
Open the reportThe resourceIds are typed as `byte[32]`, which can be invisible on a `%s` print format. ```go=69 func prettyPrint(config, config2 BridgeConfig) { fmt.Printf(` =============================================== 🎉🎉🎉 ChainBridge Successfully Deployed 🎉🎉🎉 - Chain 1 - Bridge: %s ERC20: %s ERC20 Handler: %s ERC721: %s ERC721 Handler: %s Generic Handler: %s Asset Store: %s ERC20 resourceId: %s ERC721 resourceId %s Generic resourceId %s - Chain 2 - Bridge: %s ERC20: %s ERC20 Handler: %s ERC721: %s ERC721 Handler: %s Generic Handler: %s Asset Store: %s ERC20 resourceId: %s ERC721 resourceId %s Generic resourceId %s =============================================== `, // config config.BridgeAddr, config.Erc20Addr, config.Erc20HandlerAddr, config.Erc721Addr, config.Erc721HandlerAddr, config.GenericHandlerAddr, config.AssetStoreAddr, config.Erc20ResourceID, config.Erc721ResourceID, config.GenericResourceID, // config2 config2.BridgeAddr, config2.Erc20Addr, config2.Erc20HandlerAddr, config.Erc721Addr, config.Erc721HandlerAddr, config2.GenericHandlerAddr, config2.AssetStoreAddr, config2.Erc20ResourceID, config2.Erc721ResourceID, config2.GenericResourceID, ) } ```
Open the reportThe usage of this `GasPrice` function is to read the priority field from the struct `TransactOptions` and assign appropriate gas price. ```go type TransactOptions struct { GasLimit uint64 GasPrice *big.Int Value *big.Int Nonce *big.Int ChainID *big.Int Priority uint8 } ``` In default condition the Priority is 0, which will not fall into the default branch. ```go=24 switch *priority { // slow case 0: gasPrice = []*big.Int{big.NewInt(50000000000)} // fast case 2: gasPrice = []*big.Int{big.NewInt(140000000000)} // medium default: gasPrice = []*big.Int{big.NewInt(80000000000)} } return gasPrice, nil ``` To make things clearer, swap the switch branches so that the default branch handles `case 0` and a different branch handles `case 1`.
Open the reportFile: chains/evm/calls/evmtransaction/evm-tx.go In the method `NewTransaction`, if the length of the parameter 'gasPrices []*big.Int' is zero, the gasPrices[0] will panick. Thus the following code should handle the error properly if the array passed in has the correct length. ```golang=48 return newTransaction(nonce, to, amount, gasLimit, gasPrices[0], data), nil ```
Open the reportThese 3 functions (SetupXXXHandler) are only called inside the module `local`, so they can be private. ```go=205 func SetupERC20Handler( bridgeContract *bridge.BridgeContract, erc20Contract *erc20.ERC20Contract, mintTo common.Address, conf BridgeConfig, resourceID types.ResourceID, ) ``` ```go=238 func SetupGenericHandler(bridgeContract *bridge.BridgeContract, conf BridgeConfig, resourceID types.ResourceID) ``` ```go=254 func SetupERC721Handler(bridgeContract *bridge.BridgeContract, erc721Contract *erc721.ERC721Contract, conf BridgeConfig, resourceID types.ResourceID) ```
Open the reportFile: relayer/message/message_processors.go In the function `AdjustDecimalsForERC20AmountMessageProcessor`, if the length of member of the message 'm.Payload' is zero, m.Payload[0] will panick. So the following code should handle the different cases properly. ```golang=31 amountByte, ok := m.Payload[0].([]byte) ```
Open the reportThe function `LoggerMetadata` is missing file close after opening a file. ```go=23 file, err := os.OpenFile(CliLogsFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Error().Err(fmt.Errorf("failed to create cli log file: %v", err)) } ```
Open the reportThe struct `EVMChain` is missing the comment on what it's supposed to contain. It is not that hard to figure out what the purpose and component of the struct serves, but it would be more helpful to provide a complete comment for better documentation. ```go=28 // EVMChain is struct that aggregates all data required for type EVMChain struct { listener EventListener writer ProposalExecutor blockstore *store.BlockStore config *chain.EVMConfig } ```
Open the reportThe functions `Erc20DepositHandler`, `Erc721DepositHandler` and `GenericDepositHandler` have many byte manipulation operations which fetch encoded length and then read the bytes according to the length. However, some basic size checks are missing to ensure it would not go over the boundary. Missing checks on fetched 'length' guarantees it won't go beyond `calldata`'s length. Some (not all) examples are listed below: ```go=86 recipientAddressLength := big.NewInt(0).SetBytes(calldata[32:64]) // 64 - (64 + recipient address length) is recipient address recipientAddress := calldata[64:(64 + recipientAddressLength.Int64())] ``` ```go=117 metadataLen := big.NewInt(0).SetBytes(calldata[:32]) metadata := calldata[32 : 32+metadataLen.Int64()] ``` ```go=139 recipientAddressLength := big.NewInt(0).SetBytes(calldata[32:64]) // 64 - (64 + recipient address length) is recipient address recipientAddress := calldata[64:(64 + recipientAddressLength.Int64())] // (64 + recipient address length) - ((64 + recipient address length) + 32) is metadata length metadataLength := big.NewInt(0).SetBytes( calldata[(64 + recipientAddressLength.Int64()):((64 + recipientAddressLength.Int64()) + 32)], ) ```
Open the reportFile: chains/evm/calls/transactor/signAndSend/signAndSend.go In the function `Transact`, the workflow is that: 1, call t.client.SignAndSendTransaction to send the transaction to the blockchain node. 2, using the function `WaitAndReturnTxReceipt` to check the transaction result. In step 2, if the function `WaitAndReturnTxReceipt` returns err, it doesn't mean the transaction failed entirely. For example, some error could have occurred on the remote blockchain endpoint when calling the function WaitAndReturnTxReceipt. Thus a better way is to return the transaction hash to check the receipt later. ```golang=76 if err != nil { return &common.Hash{}, err } ```
Open the reportThere is a risk that a block could be processed twice across bridge reboot. At listener.go, as shown at line 81, the id of the successfully processed block would be persisted to local db. ```go=51 func (l *EVMListener) ListenToEvents(ctx context.Context, block *big.Int, msgChan chan *message.Message, errChn chan<- error) { for { select { case <-ctx.Done(): return default: head, err := l.client.LatestBlock() if err != nil { log.Error().Err(err).Msg("Unable to get latest block") time.Sleep(l.blockRetryInterval) continue } if block == nil { block = head } // Sleep if the difference is less than needed block confirmations; (latest - current) < BlockDelay if big.NewInt(0).Sub(head, block).Cmp(l.blockConfirmations) == -1 { time.Sleep(l.blockRetryInterval) continue } log.Debug().Msgf("Queried block in listner %s", block.String()) for _, handler := range l.eventHandlers { err := handler.HandleEvent(block, msgChan) if err != nil { log.Error().Err(err).Str("DomainID", string(l.domainID)).Msgf("Unable to handle events") continue } } //Write to block store. Not a critical operation, no need to retry err = l.blockstore.StoreBlock(block, l.domainID) if err != nil { log.Error().Str("block", block.String()).Err(err).Msg("Failed to write latest block to blockstore") } block.Add(block, big.NewInt(1)) } } } ``` After the bridge is rebooted, the previously persisted block id (this block's been successfully processed at the last run before the rebooting) would be read in and passed to the function `ListenToEvents` as the starting block, which will be processed again. ```go=59 func (bs *BlockStore) GetStartBlock(domainID uint8, startBlock *big.Int, latest bool, fresh bool) (*big.Int, error) { if latest { return nil, nil } if fresh { return startBlock, nil } latestBlock, err := bs.GetLastStoredBlock(domainID) if err != nil { return nil, err } if latestBlock.Cmp(startBlock) == 1 { return latestBlock, nil } else { return startBlock, nil } } ```
Open the reportThe variable `Dst` is repeatedly verified. `ValidateMintFlags` verifies the format of `Dst`, but later it is again verified by `ProcessMintFlags`. ```go=41 Args: func(cmd *cobra.Command, args []string) error { err := ValidateMintFlags(cmd, args) if err != nil { return err } err = ProcessMintFlags(cmd, args) return err }, ``` ```go=74 func ProcessMintFlags(cmd *cobra.Command, args []string) error { Erc721Addr = common.HexToAddress(Erc721Address) if !common.IsHexAddress(Dst) { DstAddress = senderKeyPair.CommonAddress() } else { DstAddress = common.HexToAddress(Dst) } ```
Open the reportThe interface `Transactor` defined at prepare.go is the same as the one defined at transact.go. ```go=36 type Transactor interface { Transact(to *common.Address, data []byte, opts TransactOptions) (*common.Hash, error) } ```
Open the reportThe command to remove relayers is disabled, which prevents excluding a potentially malicious or misbehaving relayer. The relayers are an essential part of the bridge since they listen and filter out the necessary messages from a domain chain to a target chain. They participate in the actual bridging process by creating a interchain transfer proposals or voting on them. Thus, it is important that the relayer set is kept robust, without a portion of them going rogue or going down, which can cause problems during the transfer process. Currently, `removeRelayer` function is commented out, disabling the functionality to remove a relayer from the active set. ```go=60 /* func removeRelayer(cctx *cli.Context) error { ... } */ ```
Open the reportThese handler functions directly return nil without real data query. The actual implementations were commented out. ```go=53 func cancelProposal(cmd *cobra.Command, args []string) error { log.Debug().Msgf(` Cancel propsal Bridge address: %s Chain ID: %d Deposit nonce: %d DataHash: %s `, Bridge, DomainID, DepositNonce, DataHash) return nil } ``` ```go=53 func queryProposal(cmd *cobra.Command, args []string) error { log.Debug().Msgf(` Querying proposal Chain ID: %d Deposit nonce: %d Data hash: %s Bridge address: %s`, DomainID, DepositNonce, DataHash, Bridge) return nil } ``` ```go=51 func queryResource(cmd *cobra.Command, args []string) error { log.Debug().Msgf(` Querying resource Handler address: %s Resource ID: %s`, Handler, ResourceID) return nil } ```
Open the reportThe array gasPrices can be directly initialized with the field value without having to create first. In Go, you can directly initialize any array with initial field values. Unless the array is too big to initialize with internal values through direct expressions, you're recommended to declare the fields inside when you're creating the array. The original code makes the array first and pushes a value. ```go=50 gasPrices := make([]*big.Int, 1) gasPrices[0] = gp ``` However it can be directly initialized with the value. ```go=50 gasPrices = []*big.Int{gp} ```
Open the report`repetitiveSimulateVote` uses an unnecessary recursion logic that can be removed. The function `repetitiveSimulateVote` uses a fixed maximum tries with a starting try count to repeatedly simulate a proposal vote until it succeeds. However, we can remove the global maximum try count and directly input a number into the beginning of the function and try that number of times until it succeeds, without using recursion or global fixed maximum retries. ```go=186 func (v *EVMVoter) repetitiveSimulateVote(prop *proposal.Proposal, tries int) error { err := v.bridgeContract.SimulateVoteProposal(prop) if err != nil { if tries < maxSimulateVoteChecks { tries++ return v.repetitiveSimulateVote(prop, tries) } return err } else { return nil } } ```
Open the reportVariables `ABI` and 'Transactor` have no external calls, so they can be set to private. ```go=18 type Contract struct { contractAddress common.Address ABI abi.ABI bytecode []byte client calls.ContractCallerDispatcher transactor.Transactor } ```
Open the reportThe code can be optimized to directly return the error instead of assigning it to a variable first. For example, in `erc20/deploy.go`: ```go=43 err = ProcessDeployFlags(cmd, args) return err ``` All the other locations can be optimized, as they have the same structure as above.
Open the reportThe compiled contracts and ABI lack the necessary information to reproduce the bytecode and the ABI. The solidity contracts located under `chainbridge-solidity-v2-main` are compiled into raw bytecodes and ABI, but they lack the necessary information (compiler version and optimization parameters) to be reproduced for future maintenance. It is best to indicate the compiler settings for compilation so that one can reproduce and track the original contract for future maintenance.
Open the reportThe function `GasPrice` does not use the input parameter `priority`. In Ethereum, a user has to pay gas for the computation necessary to handle a transaction. While doing so, the user can provide a priority fee for the block producer so the block producer is incentivized to include the transaction within the block. Since the `priority` parameter represents the priority fee to be prioritized by the block producer, it is necessary for it to be included in the final gas price calculation. ```go=34 func (gasPricer *StaticGasPriceDeterminant) GasPrice(priority *uint8) ([]*big.Int, error) { gp, err := gasPricer.client.SuggestGasPrice(context.TODO()) log.Debug().Msgf("Suggested GP %s", gp.String()) ```
Open the reportThere are some hard-coded flag strings used in `utils.go` which should refer to the already declared flag strings. The flags below are already defined under `chains/evm/cli/cli.go`. ```go=33 var ( // Flags for all EVM CLI commands UrlFlagName = "url" GasLimitFlagName = "gas-limit" GasPriceFlagName = "gas-price" NetworkIdFlagName = "network" PrivateKeyFlagName = "private-key" JsonWalletFlagName = "json-wallet" JsonWalletPasswordFlagName = "json-wallet-password" Prepare = "prepare" ) ``` However, Many hard-coded strings are used in `utils.go` instead of the declared flag strings: ```go=27 gasLimitInt, err := cmd.Flags().GetUint64("gas-limit") ``` ```go=33 gasPriceInt, err := cmd.Flags().GetUint64("gas-price") ```
Open the reportThe command `generateKeyPairCmd` has a misleading description. While the description / information of the command states `If no options are specified, a Secp256k1 key will be made`, it will produce a `Secp256k1` type key regardless of any other input. The description says it will take a key type as an option: ```go=10 var generateKeyPairCmd = &cobra.Command{ Use: "generate", Short: "Generate a bridge keystore (Secp256k1)", Long: "The generate subcommand is used to generate the bridge keystore. If no options are specified, a Secp256k1 key will be made", RunE: generateKeyPair, PreRun: func(cmd *cobra.Command, args []string) { logger.LoggerMetadata(cmd.Name(), cmd.Flags()) }, } ``` However it will create a Secp256k1 key regardless of the argument. ```go=20 func generateKeyPair(cmd *cobra.Command, args []string) error { kp, err := secp256k1.GenerateKeypair() if err != nil { return err } log.Debug().Msgf("Address: %s, Private key: %x", kp.CommonAddress().String(), kp.Encode()) return nil } ```
Open the reportThe main purpose of this code snippet is not to print private key content into logfile. The flag name should be `private-key` instead of `privatekey`, now it's still printing private key into the logfile. ```go=29 flagSet.VisitAll(func(flag *pflag.Flag) { if flag.Name != "privateKey" { cmdFlagsWithArgs += fmt.Sprintf("--%s=%q ", flag.Name, flag.Value) } }) ``` At chains/evm/cli/cli.go:39 ```go=39 PrivateKeyFlagName = "private-key" ```
Open the reportCalls to the function LeftPadBytes uses an incorrect length. At `SetupEVMBridge` function in deploy.go, length 31 is given to the function `common.LeftPadBytes`. In `common.LeftPadBytes`, the final length is equal to the given number instead of the padding number, so it should set the 2nd argument of `common.LeftPadBytes` as 32 rather than 31. ```go=101 resourceIDERC20 := calls.SliceTo32Bytes(common.LeftPadBytes([]byte{0}, 31)) resourceIDGenericHandler := calls.SliceTo32Bytes(common.LeftPadBytes([]byte{1}, 31)) resourceIDERC721 := calls.SliceTo32Bytes(common.LeftPadBytes([]byte{2}, 31)) ``` ```go // LeftPadBytes zero-pads slice to the left up to length l. func LeftPadBytes(slice []byte, l int) []byte { if l <= len(slice) { return slice } padded := make([]byte, l) copy(padded[l-len(slice):], slice) return padded } ```
Open the report`Erc20Address` should be printed instead of `Recipient` chains/evm/cli/erc20/balance.go ```go=63 if !common.IsHexAddress(Erc20Address) { return fmt.Errorf("invalid recipient address %s", Recipient) } ``` The first and second parameter should be swapped. chains/evm/calls/contracts/erc20/erc20.go ```go=55 log.Debug().Msgf("Approving %s tokens for %s", target.String(), amount.String()) ```
Open the reportThe stat of the `e2e-tests.sh` script is 644, and should be 755 to make the command `make e2e-test` run correctly on all environments.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="elysium-blockchain"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us