Elysium security record

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.

Critical findings
No open critical findings
In 1 report.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.

Audits1

Newest first

AuditSecurity firmDateScopeCritical findingsReport
Elysium ⅡSecurity firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com

Findings29

Statuses are the security firm’s own, from each report.

SeverityOpenAcknowledgedFixed
Critical000
High001
Medium001
Low0027
HighCentralization on Admin with the Relayer SetElysium Ⅱ · CertiK · 13 Feb 2023Fixed

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 report
MediumUnhandled Error in Proposal Vote ExecutionElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowImplicit Address UsageElysium Ⅱ · CertiK · 13 Feb 2023Fixed

Empty 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 report
LowInappropriate Print FormatElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowInappropriate Usage of `default`Elysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowInput Param `gasPrices` Could Be EmptyElysium Ⅱ · CertiK · 13 Feb 2023Fixed

File: 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 report
LowInternal Functions Declared PublicElysium Ⅱ · CertiK · 13 Feb 2023Fixed

These 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 report
LowMissing Check for the Payload LengthElysium Ⅱ · CertiK · 13 Feb 2023Fixed

File: 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 report
LowMissing Close after Opening a FileElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowMissing CommentElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
Show 10 more
LowMissing Length Checks on Byte ManipulationsElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowMissing Transaction Hash Return ValueElysium Ⅱ · CertiK · 13 Feb 2023Fixed

File: 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 report
LowPossible Redundant Block ProcessingElysium Ⅱ · CertiK · 13 Feb 2023Fixed

There 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 report
LowRedundant Format VerificationElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowRedundant Interface DeclarationElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowUnable to Remove RelayersElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowUnimplemented CommandsElysium Ⅱ · CertiK · 13 Feb 2023Fixed

These 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 report
LowUnnecessary Array InitializationElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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
LowUnnecessary Function RecursionElysium Ⅱ · CertiK · 13 Feb 2023Fixed

`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 report
LowUnnecessary Struct Fields AccessibilityElysium Ⅱ · CertiK · 13 Feb 2023Fixed

Variables `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 report
Show 9 more
LowUnnecessary Variable AssignmentElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowUnreproducible Contract CompilationsElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowUnused Parameter in Gas Price CalculationElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowUse of Hard-Coded ValuesElysium Ⅱ · CertiK · 13 Feb 2023Fixed

There 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 report
LowWrong Command DescriptionElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowWrong flag name to protect private keyElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report
LowWrong Length Passed to LeftPadBytesElysium Ⅱ · CertiK · 13 Feb 2023Fixed

Calls 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
LowWrong Log OutputElysium Ⅱ · CertiK · 13 Feb 2023Fixed

`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 report
LowWrong Permissions on E2E Test FileElysium Ⅱ · CertiK · 13 Feb 2023Fixed

The 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 report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

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.

Content

Facts

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.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-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