mirror of
https://github.com/serai-dex/serai.git
synced 2024-10-30 08:57:36 +00:00
91 lines
2.9 KiB
Solidity
91 lines
2.9 KiB
Solidity
|
// SPDX-License-Identifier: AGPLv3
|
||
|
pragma solidity ^0.8.0;
|
||
|
|
||
|
import "./Schnorr.sol";
|
||
|
|
||
|
contract Router is Schnorr {
|
||
|
// Contract initializer
|
||
|
// TODO: Replace with a MuSig of the genesis validators
|
||
|
address public initializer;
|
||
|
|
||
|
// Nonce is incremented for each batch of transactions executed
|
||
|
uint256 public nonce;
|
||
|
|
||
|
// fixed parity for the public keys used in this contract
|
||
|
uint8 constant public KEY_PARITY = 27;
|
||
|
|
||
|
// current public key's x-coordinate
|
||
|
// note: this key must always use the fixed parity defined above
|
||
|
bytes32 public seraiKey;
|
||
|
|
||
|
struct OutInstruction {
|
||
|
address to;
|
||
|
uint256 value;
|
||
|
bytes data;
|
||
|
}
|
||
|
|
||
|
struct Signature {
|
||
|
bytes32 c;
|
||
|
bytes32 s;
|
||
|
}
|
||
|
|
||
|
// success is a uint256 representing a bitfield of transaction successes
|
||
|
event Executed(uint256 nonce, bytes32 batch, uint256 success);
|
||
|
|
||
|
// error types
|
||
|
error NotInitializer();
|
||
|
error AlreadyInitialized();
|
||
|
error InvalidKey();
|
||
|
error TooManyTransactions();
|
||
|
|
||
|
constructor() {
|
||
|
initializer = msg.sender;
|
||
|
}
|
||
|
|
||
|
// initSeraiKey can be called by the contract initializer to set the first
|
||
|
// public key, only if the public key has yet to be set.
|
||
|
function initSeraiKey(bytes32 _seraiKey) external {
|
||
|
if (msg.sender != initializer) revert NotInitializer();
|
||
|
if (seraiKey != 0) revert AlreadyInitialized();
|
||
|
if (_seraiKey == bytes32(0)) revert InvalidKey();
|
||
|
seraiKey = _seraiKey;
|
||
|
}
|
||
|
|
||
|
// updateSeraiKey validates the given Schnorr signature against the current public key,
|
||
|
// and if successful, updates the contract's public key to the given one.
|
||
|
function updateSeraiKey(
|
||
|
bytes32 _seraiKey,
|
||
|
Signature memory sig
|
||
|
) public {
|
||
|
if (_seraiKey == bytes32(0)) revert InvalidKey();
|
||
|
bytes32 message = keccak256(abi.encodePacked("updateSeraiKey", _seraiKey));
|
||
|
if (!verify(KEY_PARITY, seraiKey, message, sig.c, sig.s)) revert InvalidSignature();
|
||
|
seraiKey = _seraiKey;
|
||
|
}
|
||
|
|
||
|
// execute accepts a list of transactions to execute as well as a Schnorr signature.
|
||
|
// if signature verification passes, the given transactions are executed.
|
||
|
// if signature verification fails, this function will revert.
|
||
|
function execute(
|
||
|
OutInstruction[] calldata transactions,
|
||
|
Signature memory sig
|
||
|
) public {
|
||
|
if (transactions.length > 256) revert TooManyTransactions();
|
||
|
|
||
|
bytes32 message = keccak256(abi.encode("execute", nonce, transactions));
|
||
|
// This prevents re-entrancy from causing double spends yet does allow
|
||
|
// out-of-order execution via re-entrancy
|
||
|
nonce++;
|
||
|
if (!verify(KEY_PARITY, seraiKey, message, sig.c, sig.s)) revert InvalidSignature();
|
||
|
|
||
|
uint256 successes;
|
||
|
for(uint256 i = 0; i < transactions.length; i++) {
|
||
|
(bool success, ) = transactions[i].to.call{value: transactions[i].value, gas: 200_000}(transactions[i].data);
|
||
|
assembly {
|
||
|
successes := or(successes, shl(i, success))
|
||
|
}
|
||
|
}
|
||
|
emit Executed(nonce, message, successes);
|
||
|
}
|
||
|
}
|