// SPDX-License-Identifier: GPL-3.0-or-laterpragmasolidity ^0.6.12;import'rainbow-bridge/contracts/eth/nearbridge/contracts/AdminControlled.sol';
import'rainbow-bridge/contracts/eth/nearbridge/contracts/Borsh.sol';
import'rainbow-bridge/contracts/eth/nearprover/contracts/ProofDecoder.sol';
import { INearProver, ProofKeeper } from'./ProofKeeper.sol';
contractEthCustodianisProofKeeper, AdminControlled{
uintconstant UNPAUSED_ALL =0;
uintconstant PAUSED_DEPOSIT_TO_EVM =1<<0;
uintconstant PAUSED_DEPOSIT_TO_NEAR =1<<1;
uintconstant PAUSED_WITHDRAW =1<<2;
eventDeposited (addressindexed sender,
string recipient,
uint256 amount,
uint256 fee
);
eventWithdrawn(addressindexed recipient,
uint128 amount
);
// Function output from burning nETH on Near side.structBurnResult {
uint128 amount;
address recipient;
address ethCustodian;
}
/// EthCustodian is linked to the EVM on NEAR side./// It also links to the prover that it uses to withdraw the tokens.constructor(bytesmemory nearEvm,
INearProver prover,
uint64 minBlockAcceptanceHeight,
address _admin,
uint pausedFlags
)
AdminControlled(_admin, pausedFlags)
ProofKeeper(nearEvm, prover, minBlockAcceptanceHeight)
public{
}
/// Deposits the specified amount of provided ETH (except from the relayer's fee) into the smart contract./// `ethRecipientOnNear` - the ETH address of the recipient in NEAR EVM/// `fee` - the amount of fee that will be paid to the near-relayer in nETH.functiondepositToEVM(stringmemory ethRecipientOnNear,
uint256 fee
)
externalpayablepausable(PAUSED_DEPOSIT_TO_EVM)
{
require(
fee <msg.value,
'The fee cannot be bigger than the transferred amount.'
);
stringmemory separator =':';
stringmemory protocolMessage =string(
abi.encodePacked(
string(nearProofProducerAccount_),
separator, ethRecipientOnNear
)
);
emit Deposited(
msg.sender,
protocolMessage,
msg.value,
fee
);
}
/// Deposits the specified amount of provided ETH (except from the relayer's fee) into the smart contract./// `nearRecipientAccountId` - the AccountID of the recipient in NEAR/// `fee` - the amount of fee that will be paid to the near-relayer in nETH.functiondepositToNear(stringmemory nearRecipientAccountId,
uint256 fee
)
externalpayablepausable(PAUSED_DEPOSIT_TO_NEAR)
{
require(
fee <msg.value,
'The fee cannot be bigger than the transferred amount.'
);
emit Deposited(
msg.sender,
nearRecipientAccountId,
msg.value,
fee
);
}
/// Withdraws the appropriate amount of ETH which is encoded in `proofData`functionwithdraw(bytescalldata proofData,
uint64 proofBlockHeight
)
externalpausable(PAUSED_WITHDRAW)
{
ProofDecoder.ExecutionStatus memory status = _parseAndConsumeProof(proofData, proofBlockHeight);
BurnResult memory result = _decodeBurnResult(status.successValue);
require(
result.ethCustodian ==address(this),
'Can only withdraw coins that were expected for the current contract'
);
payable(result.recipient).transfer(result.amount);
emit Withdrawn(
result.recipient,
result.amount
);
}
function_decodeBurnResult(bytesmemory data)
internalpurereturns (BurnResult memory result)
{
Borsh.Data memory borshData = Borsh.from(data);
result.amount = borshData.decodeU128();
bytes20 recipient = borshData.decodeBytes20();
result.recipient =address(uint160(recipient));
bytes20 ethCustodian = borshData.decodeBytes20();
result.ethCustodian =address(uint160(ethCustodian));
}
}
pragmasolidity ^0.6;import"@openzeppelin/contracts/math/SafeMath.sol";
import"./Borsh.sol";
libraryNearDecoder{
usingBorshforBorsh.Data;
usingNearDecoderforBorsh.Data;
structPublicKey {
uint8 enumIndex;
Borsh.ED25519PublicKey ed25519;
Borsh.SECP256K1PublicKey secp256k1;
}
functiondecodePublicKey(Borsh.Data memory data) internalpurereturns (PublicKey memory key) {
key.enumIndex = data.decodeU8();
if (key.enumIndex ==0) {
key.ed25519 = data.decodeED25519PublicKey();
} elseif (key.enumIndex ==1) {
key.secp256k1 = data.decodeSECP256K1PublicKey();
} else {
revert("NearBridge: Only ED25519 and SECP256K1 public keys are supported");
}
}
structValidatorStake {
string account_id;
PublicKey public_key;
uint128 stake;
}
functiondecodeValidatorStake(Borsh.Data memory data) internalpurereturns (ValidatorStake memory validatorStake) {
validatorStake.account_id =string(data.decodeBytes());
validatorStake.public_key = data.decodePublicKey();
validatorStake.stake = data.decodeU128();
}
structOptionalValidatorStakes {
bool none;
ValidatorStake[] validatorStakes;
bytes32 hash; // Additional computable element
}
functiondecodeOptionalValidatorStakes(Borsh.Data memory data)
internalviewreturns (OptionalValidatorStakes memory stakes)
{
stakes.none = (data.decodeU8() ==0);
if (!stakes.none) {
uint256 start = data.offset;
stakes.validatorStakes =new ValidatorStake[](data.decodeU32());
for (uint i =0; i < stakes.validatorStakes.length; i++) {
stakes.validatorStakes[i] = data.decodeValidatorStake();
}
uint256 stop = data.offset;
data.offset = start;
stakes.hash = data.peekSha256(stop - start);
data.offset = stop;
}
}
structSignature {
uint8 enumIndex;
Borsh.ED25519Signature ed25519;
Borsh.SECP256K1Signature secp256k1;
}
functiondecodeSignature(Borsh.Data memory data) internalpurereturns (Signature memory sig) {
sig.enumIndex = data.decodeU8();
if (sig.enumIndex ==0) {
sig.ed25519 = data.decodeED25519Signature();
} elseif (sig.enumIndex ==1) {
sig.secp256k1 = data.decodeSECP256K1Signature();
} else {
revert("NearBridge: Only ED25519 and SECP256K1 signatures are supported");
}
}
structOptionalSignature {
bool none;
Signature signature;
}
functiondecodeOptionalSignature(Borsh.Data memory data) internalpurereturns (OptionalSignature memory sig) {
sig.none = (data.decodeU8() ==0);
if (!sig.none) {
sig.signature = data.decodeSignature();
}
}
structLightClientBlock {
bytes32 prev_block_hash;
bytes32 next_block_inner_hash;
BlockHeaderInnerLite inner_lite;
bytes32 inner_rest_hash;
OptionalValidatorStakes next_bps;
OptionalSignature[] approvals_after_next;
bytes32 hash;
bytes32 next_hash;
}
structInitialValidators {
ValidatorStake[] validator_stakes;
}
functiondecodeInitialValidators(Borsh.Data memory data)
internalviewreturns (InitialValidators memory validators)
{
validators.validator_stakes =new ValidatorStake[](data.decodeU32());
for (uint i =0; i < validators.validator_stakes.length; i++) {
validators.validator_stakes[i] = data.decodeValidatorStake();
}
}
functiondecodeLightClientBlock(Borsh.Data memory data) internalviewreturns (LightClientBlock memory header) {
header.prev_block_hash = data.decodeBytes32();
header.next_block_inner_hash = data.decodeBytes32();
header.inner_lite = data.decodeBlockHeaderInnerLite();
header.inner_rest_hash = data.decodeBytes32();
header.next_bps = data.decodeOptionalValidatorStakes();
header.approvals_after_next =new OptionalSignature[](data.decodeU32());
for (uint i =0; i < header.approvals_after_next.length; i++) {
header.approvals_after_next[i] = data.decodeOptionalSignature();
}
header.hash =sha256(
abi.encodePacked(
sha256(abi.encodePacked(header.inner_lite.hash, header.inner_rest_hash)),
header.prev_block_hash
)
);
header.next_hash =sha256(abi.encodePacked(header.next_block_inner_hash, header.hash));
}
structBlockHeaderInnerLite {
uint64 height; /// Height of this block since the genesis block (height 0).bytes32 epoch_id; /// Epoch start hash of this block's epoch. Used for retrieving validator informationbytes32 next_epoch_id;
bytes32 prev_state_root; /// Root hash of the state at the previous block.bytes32 outcome_root; /// Root of the outcomes of transactions and receipts.uint64 timestamp; /// Timestamp at which the block was built.bytes32 next_bp_hash; /// Hash of the next epoch block producers setbytes32 block_merkle_root;
bytes32 hash; // Additional computable element
}
functiondecodeBlockHeaderInnerLite(Borsh.Data memory data)
internalviewreturns (BlockHeaderInnerLite memory header)
{
header.hash = data.peekSha256(208);
header.height = data.decodeU64();
header.epoch_id = data.decodeBytes32();
header.next_epoch_id = data.decodeBytes32();
header.prev_state_root = data.decodeBytes32();
header.outcome_root = data.decodeBytes32();
header.timestamp = data.decodeU64();
header.next_bp_hash = data.decodeBytes32();
header.block_merkle_root = data.decodeBytes32();
}
}
Código Fuente del Contrato
Archivo 6 de 8: ProofDecoder.sol
pragmasolidity ^0.6;import"../../nearbridge/contracts/Borsh.sol";
import"../../nearbridge/contracts/NearDecoder.sol";
libraryProofDecoder{
usingBorshforBorsh.Data;
usingProofDecoderforBorsh.Data;
usingNearDecoderforBorsh.Data;
structFullOutcomeProof {
ExecutionOutcomeWithIdAndProof outcome_proof;
MerklePath outcome_root_proof; // TODO: now empty array
BlockHeaderLight block_header_lite;
MerklePath block_proof;
}
functiondecodeFullOutcomeProof(Borsh.Data memory data) internalviewreturns (FullOutcomeProof memory proof) {
proof.outcome_proof = data.decodeExecutionOutcomeWithIdAndProof();
proof.outcome_root_proof = data.decodeMerklePath();
proof.block_header_lite = data.decodeBlockHeaderLight();
proof.block_proof = data.decodeMerklePath();
}
structBlockHeaderLight {
bytes32 prev_block_hash;
bytes32 inner_rest_hash;
NearDecoder.BlockHeaderInnerLite inner_lite;
bytes32 hash; // Computable
}
functiondecodeBlockHeaderLight(Borsh.Data memory data) internalviewreturns (BlockHeaderLight memory header) {
header.prev_block_hash = data.decodeBytes32();
header.inner_rest_hash = data.decodeBytes32();
header.inner_lite = data.decodeBlockHeaderInnerLite();
header.hash =sha256(
abi.encodePacked(
sha256(abi.encodePacked(header.inner_lite.hash, header.inner_rest_hash)),
header.prev_block_hash
)
);
}
structExecutionStatus {
uint8 enumIndex;
bool unknown;
bool failed;
bytes successValue; /// The final action succeeded and returned some value or an empty vec.bytes32 successReceiptId; /// The final action of the receipt returned a promise or the signed/// transaction was converted to a receipt. Contains the receipt_id of the generated receipt.
}
functiondecodeExecutionStatus(Borsh.Data memory data)
internalpurereturns (ExecutionStatus memory executionStatus)
{
executionStatus.enumIndex = data.decodeU8();
if (executionStatus.enumIndex ==0) {
executionStatus.unknown =true;
} elseif (executionStatus.enumIndex ==1) {
//revert("NearDecoder: decodeExecutionStatus failure case not implemented yet");// Can avoid revert since ExecutionStatus is latest field in all parent structures
executionStatus.failed =true;
} elseif (executionStatus.enumIndex ==2) {
executionStatus.successValue = data.decodeBytes();
} elseif (executionStatus.enumIndex ==3) {
executionStatus.successReceiptId = data.decodeBytes32();
} else {
revert("NearDecoder: decodeExecutionStatus index out of range");
}
}
structExecutionOutcome {
bytes[] logs; /// Logs from this transaction or receipt.bytes32[] receipt_ids; /// Receipt IDs generated by this transaction or receipt.uint64 gas_burnt; /// The amount of the gas burnt by the given transaction or receipt.uint128 tokens_burnt; /// The total number of the tokens burnt by the given transaction or receipt.bytes executor_id; /// Hash of the transaction or receipt id that produced this outcome.
ExecutionStatus status; /// Execution status. Contains the result in case of successful execution.bytes32[] merkelization_hashes;
}
functiondecodeExecutionOutcome(Borsh.Data memory data) internalviewreturns (ExecutionOutcome memory outcome) {
outcome.logs =newbytes[](data.decodeU32());
for (uint i =0; i < outcome.logs.length; i++) {
outcome.logs[i] = data.decodeBytes();
}
uint256 start = data.offset;
outcome.receipt_ids =newbytes32[](data.decodeU32());
for (uint i =0; i < outcome.receipt_ids.length; i++) {
outcome.receipt_ids[i] = data.decodeBytes32();
}
outcome.gas_burnt = data.decodeU64();
outcome.tokens_burnt = data.decodeU128();
outcome.executor_id = data.decodeBytes();
outcome.status = data.decodeExecutionStatus();
uint256 stop = data.offset;
outcome.merkelization_hashes =newbytes32[](1+ outcome.logs.length);
data.offset = start;
outcome.merkelization_hashes[0] = data.peekSha256(stop - start);
data.offset = stop;
for (uint i =0; i < outcome.logs.length; i++) {
outcome.merkelization_hashes[i +1] =sha256(outcome.logs[i]);
}
}
structExecutionOutcomeWithId {
bytes32 id; /// The transaction hash or the receipt ID.
ExecutionOutcome outcome;
bytes32 hash;
}
functiondecodeExecutionOutcomeWithId(Borsh.Data memory data)
internalviewreturns (ExecutionOutcomeWithId memory outcome)
{
outcome.id = data.decodeBytes32();
outcome.outcome = data.decodeExecutionOutcome();
uint256 len =1+ outcome.outcome.merkelization_hashes.length;
outcome.hash =sha256(
abi.encodePacked(
uint8((len >>0) &0xFF),
uint8((len >>8) &0xFF),
uint8((len >>16) &0xFF),
uint8((len >>24) &0xFF),
outcome.id,
outcome.outcome.merkelization_hashes
)
);
}
structMerklePathItem {
bytes32 hash;
uint8 direction; // 0 = left, 1 = right
}
functiondecodeMerklePathItem(Borsh.Data memory data) internalpurereturns (MerklePathItem memory item) {
item.hash = data.decodeBytes32();
item.direction = data.decodeU8();
require(item.direction <2, "ProofDecoder: MerklePathItem direction should be 0 or 1");
}
structMerklePath {
MerklePathItem[] items;
}
functiondecodeMerklePath(Borsh.Data memory data) internalpurereturns (MerklePath memory path) {
path.items =new MerklePathItem[](data.decodeU32());
for (uint i =0; i < path.items.length; i++) {
path.items[i] = data.decodeMerklePathItem();
}
}
structExecutionOutcomeWithIdAndProof {
MerklePath proof;
bytes32 block_hash;
ExecutionOutcomeWithId outcome_with_id;
}
functiondecodeExecutionOutcomeWithIdAndProof(Borsh.Data memory data)
internalviewreturns (ExecutionOutcomeWithIdAndProof memory outcome)
{
outcome.proof = data.decodeMerklePath();
outcome.block_hash = data.decodeBytes32();
outcome.outcome_with_id = data.decodeExecutionOutcomeWithId();
}
}
Código Fuente del Contrato
Archivo 7 de 8: ProofKeeper.sol
// SPDX-License-Identifier: GPL-3.0-or-laterpragmasolidity ^0.6.12;import'rainbow-bridge/contracts/eth/nearprover/contracts/INearProver.sol';
import'rainbow-bridge/contracts/eth/nearprover/contracts/ProofDecoder.sol';
import'rainbow-bridge/contracts/eth/nearbridge/contracts/Borsh.sol';
contractProofKeeper{
usingBorshforBorsh.Data;
usingProofDecoderforBorsh.Data;
INearProver public prover_;
bytespublic nearProofProducerAccount_;
/// Proofs from blocks that are below the acceptance height will be rejected.// If `minBlockAcceptanceHeight_` value is zero - proofs from block with any height are accepted.uint64public minBlockAcceptanceHeight_;
// OutcomeReciptId -> Usedmapping(bytes32=>bool) public usedEvents_;
constructor(bytesmemory nearProofProducerAccount,
INearProver prover,
uint64 minBlockAcceptanceHeight
)
public{
require(
nearProofProducerAccount.length>0,
'Invalid Near ProofProducer address'
);
require(
address(prover) !=address(0),
'Invalid Near prover address'
);
nearProofProducerAccount_ = nearProofProducerAccount;
prover_ = prover;
minBlockAcceptanceHeight_ = minBlockAcceptanceHeight;
}
/// Parses the provided proof and consumes it if it's not already used./// The consumed event cannot be reused for future calls.function_parseAndConsumeProof(bytesmemory proofData,
uint64 proofBlockHeight
)
internalreturns(ProofDecoder.ExecutionStatus memory result)
{
require(
proofBlockHeight >= minBlockAcceptanceHeight_,
'Proof is from the ancient block'
);
require(
prover_.proveOutcome(proofData,proofBlockHeight),
'Proof should be valid'
);
// Unpack the proof and extract the execution outcome.
Borsh.Data memory borshData = Borsh.from(proofData);
ProofDecoder.FullOutcomeProof memory fullOutcomeProof =
borshData.decodeFullOutcomeProof();
require(
borshData.finished(),
'Argument should be exact borsh serialization'
);
bytes32 receiptId =
fullOutcomeProof.outcome_proof.outcome_with_id.outcome.receipt_ids[0];
require(
!usedEvents_[receiptId],
'The burn event cannot be reused'
);
usedEvents_[receiptId] =true;
require(
keccak256(fullOutcomeProof.outcome_proof.outcome_with_id.outcome.executor_id) ==keccak256(nearProofProducerAccount_),
'Can only withdraw coins from the linked proof producer on Near blockchain'
);
result = fullOutcomeProof.outcome_proof.outcome_with_id.outcome.status;
require(
!result.failed,
'Cannot use failed execution outcome for unlocking the tokens'
);
require(
!result.unknown,
'Cannot use unknown execution outcome for unlocking the tokens'
);
}
}
Código Fuente del Contrato
Archivo 8 de 8: SafeMath.sol
// SPDX-License-Identifier: MITpragmasolidity >=0.6.0 <0.8.0;/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/librarySafeMath{
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/functiontryAdd(uint256 a, uint256 b) internalpurereturns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/functiontrySub(uint256 a, uint256 b) internalpurereturns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/functiontryMul(uint256 a, uint256 b) internalpurereturns (bool, uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the// benefit is lost if 'b' is also tested.// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522if (a ==0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/functiontryDiv(uint256 a, uint256 b) internalpurereturns (bool, uint256) {
if (b ==0) return (false, 0);
return (true, a / b);
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/functiontryMod(uint256 a, uint256 b) internalpurereturns (bool, uint256) {
if (b ==0) return (false, 0);
return (true, a % b);
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/functionadd(uint256 a, uint256 b) internalpurereturns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/functionsub(uint256 a, uint256 b) internalpurereturns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/functionmul(uint256 a, uint256 b) internalpurereturns (uint256) {
if (a ==0) return0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/functiondiv(uint256 a, uint256 b) internalpurereturns (uint256) {
require(b >0, "SafeMath: division by zero");
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/functionmod(uint256 a, uint256 b) internalpurereturns (uint256) {
require(b >0, "SafeMath: modulo by zero");
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/functionsub(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/functiondiv(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b >0, errorMessage);
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/functionmod(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b >0, errorMessage);
return a % b;
}
}