Skip to content

Commit

Permalink
Refactor channel architecture (paritytech#308)
Browse files Browse the repository at this point in the history
  • Loading branch information
vgeddes authored Mar 9, 2021
1 parent e19c4d6 commit 503042c
Show file tree
Hide file tree
Showing 66 changed files with 3,480 additions and 1,659 deletions.
13 changes: 9 additions & 4 deletions ethereum/contracts/BasicOutboundChannel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import "./OutboundChannel.sol";

// BasicOutboundChannel is a basic channel that just sends messages with a nonce.
contract BasicOutboundChannel is OutboundChannel {
constructor() {
nonce = 0;
}

uint64 public nonce;

event Message(
address source,
uint64 nonce,
bytes payload
);

/**
* @dev Sends a message across the channel
*/
function submit(bytes memory payload) public override {
function submit(address, bytes calldata payload) external override {
nonce = nonce + 1;
emit Message(msg.sender, nonce, payload);
}
Expand Down
57 changes: 35 additions & 22 deletions ethereum/contracts/BaseDOTApp.sol → ethereum/contracts/DOTApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,31 @@
pragma solidity >=0.7.6;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "./WrappedToken.sol";
import "./ScaleCodec.sol";
import "./OutboundChannel.sol";

enum ChannelId {Basic, Incentivized}

abstract contract BaseDOTApp {
contract DOTApp is AccessControl {
using ScaleCodec for uint128;

bytes32 public constant FEE_BURNER_ROLE = keccak256("FEE_BURNER_ROLE");

mapping(ChannelId => Channel) public channels;

bytes2 constant UNLOCK_CALL = 0x0e01;

/*
* Smallest part of DOT/KSM/ROC that is not divisible when increasing
* precision to 18 decimal places.
*
* This is used for converting between native and wrapped
* representations of DOT/KSM/ROC.
*/
uint256 private granularity;

WrappedToken public token;

struct Channel {
Expand All @@ -25,6 +37,7 @@ abstract contract BaseDOTApp {
constructor(
string memory _name,
string memory _symbol,
uint256 _decimals,
Channel memory _basic,
Channel memory _incentivized
) {
Expand All @@ -38,6 +51,10 @@ abstract contract BaseDOTApp {
Channel storage c2 = channels[ChannelId.Incentivized];
c2.inbound = _incentivized.inbound;
c2.outbound = _incentivized.outbound;

_setupRole(FEE_BURNER_ROLE, _incentivized.outbound);

granularity = 10 ** (18 - _decimals);
}

function burn(bytes32 _recipient, uint256 _amount, ChannelId _channelId) public {
Expand All @@ -46,21 +63,28 @@ abstract contract BaseDOTApp {
_channelId == ChannelId.Incentivized,
"Invalid channel ID"
);
require(_amount % granularity() == 0, "Invalid Granularity");
require(_amount % granularity == 0, "Invalid Granularity");

token.burn(msg.sender, _amount, abi.encodePacked(_recipient));

OutboundChannel channel = OutboundChannel(channels[_channelId].outbound);

bytes memory call = encodeCall(msg.sender, _recipient, unwrap(_amount));
channel.submit(call);
channel.submit(msg.sender, call);
}

function mint(bytes32 _sender, address _recipient, uint128 _amount) public {
// TODO: Ensure message sender is a known inbound channel
token.mint(_recipient, wrap(_amount), abi.encodePacked(_sender));
}

function burnFee(address _account, uint256 _amount) external returns (uint128) {
require(hasRole(FEE_BURNER_ROLE, msg.sender), "ACCESS_FORBIDDEN");
require(_amount % granularity == 0, "INVALID_GRANULARITY");
token.burn(_account, _amount, "");
return unwrap(_amount);
}

function encodeCall(address _sender, bytes32 _recipient, uint128 _amount)
private
pure
Expand All @@ -79,37 +103,26 @@ abstract contract BaseDOTApp {
/*
* Convert native DOT/KSM/ROC to the wrapped equivalent.
*
* SAFETY: No need for SafeMath.mul since its impossible to overflow
* when 0 <= granularity <= 10 ^ 8, as specified by DOTAppDecimals10.sol
* and DOTAppDecimals12.sol.
* SAFETY: No need for SafeMath.mul as overflow is not possible for
* 0 <= granularity <= 10 ^ 8.
*
* Can verify in Rust using this snippet:
*
* let granularity = U256::from(100000000u64);
* U256::from(u128::MAX).checked_mul(granularity).unwrap();
*
*/
function wrap(uint128 _value) pure internal returns (uint256) {
return uint256(_value) * granularity();
function wrap(uint128 _value) view internal returns (uint256) {
return uint256(_value) * granularity;
}

/*
* Convert wrapped DOT/KSM/ROC to its native equivalent.
*
* SAFETY: No need for SafeMath.div since granularity() resolves to a non-zero
* constant (See DOTAppDecimals10.sol and DOTAppDecimals12.sol)
* SAFETY: No need for SafeMath.div since granularity is
* configured to be non-zero.
*/
function unwrap(uint256 _value) pure internal returns (uint128) {
return uint128(_value / granularity());
function unwrap(uint256 _value) view internal returns (uint128) {
return uint128(_value / granularity);
}

/**
* Smallest part of DOT/KSM/ROC that is not divisible when increasing
* precision to 18 decimal places.
*
* This is used for converting between native and wrapped
* representations of DOT/KSM/ROC.
*/
function granularity() pure internal virtual returns (uint256);

}
24 changes: 0 additions & 24 deletions ethereum/contracts/DOTAppDecimals10.sol

This file was deleted.

24 changes: 0 additions & 24 deletions ethereum/contracts/DOTAppDecimals12.sol

This file was deleted.

2 changes: 1 addition & 1 deletion ethereum/contracts/ERC20App.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ contract ERC20App {

OutboundChannel channel =
OutboundChannel(channels[_channelId].outbound);
channel.submit(call);
channel.submit(msg.sender, call);
}

function unlock(
Expand Down
2 changes: 1 addition & 1 deletion ethereum/contracts/ETHApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ contract ETHApp {

OutboundChannel channel =
OutboundChannel(channels[_channelId].outbound);
channel.submit(call);
channel.submit(msg.sender, call);
}

function unlock(
Expand Down
16 changes: 9 additions & 7 deletions ethereum/contracts/IncentivizedOutboundChannel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import "./OutboundChannel.sol";
// IncentivizedOutboundChannel is a channel that sends ordered messages with an increasing nonce. It will have incentivization too.
contract IncentivizedOutboundChannel is OutboundChannel {

constructor() {
nonce = 0;
}
// Nonce for last submitted message
uint64 public nonce;

event Message(
address source,
uint64 nonce,
bytes payload
);

/**
* @dev Sends a message across the channel
*/
function submit(bytes memory payload)
public
override
{
function submit(address, bytes calldata payload) external override {
nonce = nonce + 1;
emit Message(msg.sender, nonce, payload);
}
Expand Down
20 changes: 2 additions & 18 deletions ethereum/contracts/OutboundChannel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,6 @@
pragma solidity >=0.7.6;
pragma experimental ABIEncoderV2;

// OutboundChannel contains methods that all outgoing channels must implement
abstract contract OutboundChannel {

// Nonce for last submitted message
uint64 public nonce;

event Message(
address source,
uint64 nonce,
bytes payload
);

/**
* @dev Sends a message across the channel
*/
function submit(bytes memory payload)
public
virtual;
interface OutboundChannel {
function submit(address origin, bytes calldata payload) external;
}
4 changes: 2 additions & 2 deletions ethereum/contracts/WrappedToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ contract WrappedToken is ERC777, Ownable {
_mint(recipient, amount, data, "");
}

// Don't allow users to directly burn their SnowDOT via the IERC777 burn API, as it won't have
// the desired effect.
// Don't allow users to directly burn their wrapped tokens via the IERC777 burn API, as it won't redeem
// the native tokens on substrate.

function burn(uint256, bytes memory) public pure override {
revert("not-supported");
Expand Down
8 changes: 4 additions & 4 deletions ethereum/migrations/2_next.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const ScaleCodec = artifacts.require("ScaleCodec");
const ETHApp = artifacts.require("ETHApp");
const ERC20App = artifacts.require("ERC20App");
const DOTAppDecimals10 = artifacts.require("DOTAppDecimals10");
const DOTAppDecimals12 = artifacts.require("DOTAppDecimals12");
const DOTApp = artifacts.require("DOTApp");
const TestToken = artifacts.require("TestToken");

const channels = {
Expand Down Expand Up @@ -37,7 +36,7 @@ module.exports = function(deployer, network, accounts) {

// Link libraries to applications
await deployer.deploy(ScaleCodec);
deployer.link(ScaleCodec, [ETHApp, ERC20App, DOTAppDecimals10, DOTAppDecimals12]);
deployer.link(ScaleCodec, [ETHApp, ERC20App, DOTApp]);

// Deploy applications
await deployer.deploy(
Expand Down Expand Up @@ -78,9 +77,10 @@ module.exports = function(deployer, network, accounts) {
// only deploy this contract to non-development networks. The unit tests deploy this contract themselves.
if (network === 'ropsten' || network === 'e2e_test') {
await deployer.deploy(
DOTAppDecimals12, // On Kusama and Rococo, KSM/ROC tokens have 12 decimal places
DOTApp,
"Snowfork DOT",
"SnowDOT",
12, // On Kusama and Rococo, KSM/ROC tokens have 12 decimal places
{
inbound: channels.basic.inbound.instance.address,
outbound: channels.basic.outbound.instance.address,
Expand Down
44 changes: 25 additions & 19 deletions ethereum/test/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const ethers = require("ethers");
const BigNumber = require('bignumber.js');
const rlp = require("rlp");
const { ethers } = require("ethers");

const assert = require('chai').assert;

const channelContracts = {
basic: {
Expand All @@ -13,32 +15,35 @@ const channelContracts = {
},
};

const confirmChannelSend = (channelEvent, channelAddress, sendingAppAddress, expectedNonce = 0, expectedPayload) => {
outChannelLogFields = [
{
type: 'address',
name: 'source'
},
{
type: 'uint64',
name: 'nonce'
},
{
type: 'bytes',
name: 'payload',
}
];
const confirmBasicChannelSend = (channelEvent, channelAddress, sendingAppAddress, expectedNonce = 0, expectedPayload) => {
var abi = ["event Message(address source, uint64 nonce, bytes payload)"];
var iface = new ethers.utils.Interface(abi);
let decodedEvent = iface.decodeEventLog('Message(address,uint64,bytes)', channelEvent.data, channelEvent.topics);

const decodedEvent = web3.eth.abi.decodeLog(outChannelLogFields, channelEvent.data, channelEvent.topics);
channelEvent.address.should.be.equal(channelAddress);
decodedEvent.source.should.be.equal(sendingAppAddress);

assert(decodedEvent.nonce.eq(ethers.BigNumber.from(expectedNonce)));
if (expectedPayload) {
decodedEvent.payload.should.be.equal(expectedPayload);
}
};

const confirmIncentivizedChannelSend = (channelEvent, channelAddress, sendingAppAddress, expectedNonce = 0, expectedPayload) => {
var abi = ["event Message(address source, uint64 nonce, bytes payload)"];
var iface = new ethers.utils.Interface(abi);
let decodedEvent = iface.decodeEventLog('Message(address,uint64,bytes)', channelEvent.data, channelEvent.topics);

channelEvent.address.should.be.equal(channelAddress);
decodedEvent.source.should.be.equal(sendingAppAddress);
decodedEvent.nonce.should.be.equal('' + expectedNonce);

assert(decodedEvent.nonce.eq(ethers.BigNumber.from(expectedNonce)));
if (expectedPayload) {
decodedEvent.payload.should.be.equal(expectedPayload);
}
};


const confirmUnlock = (rawEvent, ethAppAddress, expectedRecipient, expectedAmount) => {
unlockLogFields = [
{
Expand Down Expand Up @@ -198,7 +203,8 @@ const encodeLog = (log) => {
}

module.exports = {
confirmChannelSend,
confirmBasicChannelSend,
confirmIncentivizedChannelSend,
confirmUnlock,
confirmUnlockTokens,
confirmMessageDispatched,
Expand Down
Loading

0 comments on commit 503042c

Please sign in to comment.