Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Commit

Permalink
Relayer Rewards + Wrapped DOT (paritytech#271)
Browse files Browse the repository at this point in the history
  • Loading branch information
vgeddes authored Mar 2, 2021
1 parent 84b5d4e commit 09b49c5
Show file tree
Hide file tree
Showing 33 changed files with 1,696 additions and 119 deletions.
115 changes: 115 additions & 0 deletions ethereum/contracts/BaseDOTApp.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6;
pragma experimental ABIEncoderV2;

import "./WrappedToken.sol";
import "./ScaleCodec.sol";
import "./OutboundChannel.sol";

enum ChannelId {Basic, Incentivized}

abstract contract BaseDOTApp {
using ScaleCodec for uint128;

mapping(ChannelId => Channel) public channels;

bytes2 constant UNLOCK_CALL = 0x0e01;

WrappedToken public token;

struct Channel {
address inbound;
address outbound;
}

constructor(
string memory _name,
string memory _symbol,
Channel memory _basic,
Channel memory _incentivized
) {
address[] memory defaultOperators;
token = new WrappedToken(_name, _symbol, defaultOperators);

Channel storage c1 = channels[ChannelId.Basic];
c1.inbound = _basic.inbound;
c1.outbound = _basic.outbound;

Channel storage c2 = channels[ChannelId.Incentivized];
c2.inbound = _incentivized.inbound;
c2.outbound = _incentivized.outbound;
}

function burn(bytes32 _recipient, uint256 _amount, ChannelId _channelId) public {
require(
_channelId == ChannelId.Basic ||
_channelId == ChannelId.Incentivized,
"Invalid channel ID"
);
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);
}

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 encodeCall(address _sender, bytes32 _recipient, uint128 _amount)
private
pure
returns (bytes memory)
{
return
abi.encodePacked(
UNLOCK_CALL,
_sender,
byte(0x00), // Encoding recipient as MultiAddress::Id
_recipient,
_amount.encode128()
);
}

/*
* 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.
*
* 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();
}

/*
* 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)
*/
function unwrap(uint256 _value) pure 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: 24 additions & 0 deletions ethereum/contracts/DOTAppDecimals10.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6;
pragma experimental ABIEncoderV2;

import "./BaseDOTApp.sol";

contract DOTAppDecimals10 is BaseDOTApp {
// Polkadot (DOT) has 10 decimal places
uint256 constant internal DECIMALS = 10;
uint256 constant internal GRANULARITY = 10 ** (18 - DECIMALS);

constructor(
string memory _name,
string memory _symbol,
Channel memory _basic,
Channel memory _incentivized
)
BaseDOTApp(_name, _symbol, _basic, _incentivized)
{ }

function granularity() pure internal override returns (uint256) {
return GRANULARITY;
}
}
24 changes: 24 additions & 0 deletions ethereum/contracts/DOTAppDecimals12.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6;
pragma experimental ABIEncoderV2;

import "./BaseDOTApp.sol";

contract DOTAppDecimals12 is BaseDOTApp {
// Kusama (KSM) and Rococo (ROC) have 12 decimal places
uint256 constant internal DECIMALS = 12;
uint256 constant internal GRANULARITY = 10 ** (18 - DECIMALS);

constructor(
string memory _name,
string memory _symbol,
Channel memory _basic,
Channel memory _incentivized
)
BaseDOTApp(_name, _symbol, _basic, _incentivized)
{ }

function granularity() pure internal override returns (uint256) {
return GRANULARITY;
}
}
36 changes: 36 additions & 0 deletions ethereum/contracts/WrappedToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC777/ERC777.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract WrappedToken is ERC777, Ownable {

constructor(
string memory _name,
string memory _symbol,
address[] memory _defaultOperators
)
ERC777(_name, _symbol, _defaultOperators)
{ }

function burn(address sender, uint256 amount, bytes memory data) external onlyOwner {
_burn(sender, amount, data, "");
}

function mint(address recipient, uint256 amount, bytes memory data) external onlyOwner {
_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.

function burn(uint256, bytes memory) public pure override {
revert("not-supported");
}

function operatorBurn(address, uint256, bytes memory, bytes memory) public pure override {
revert("not-supported");
}
}
31 changes: 30 additions & 1 deletion ethereum/migrations/2_next.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
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 TestToken = artifacts.require("TestToken");

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

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

// Deploy applications
await deployer.deploy(
Expand Down Expand Up @@ -63,5 +65,32 @@ module.exports = function(deployer, network, accounts) {
);

await deployer.deploy(TestToken, 100000000, "Test Token", "TEST");

// Deploy ERC1820 Registry for our E2E stack.
if (network === 'e2e_test') {

require('@openzeppelin/test-helpers/configure')({ web3 });
const { singletons } = require('@openzeppelin/test-helpers');

await singletons.ERC1820Registry(accounts[0]);
}

// 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
"Snowfork DOT",
"SnowDOT",
{
inbound: channels.basic.inbound.instance.address,
outbound: channels.basic.outbound.instance.address,
},
{
inbound: channels.incentivized.inbound.instance.address,
outbound: channels.incentivized.outbound.instance.address,
},
);
}

})
};
3 changes: 2 additions & 1 deletion ethereum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"license": "Apache-2.0",
"dependencies": {
"@iarna/toml": "^2.2.5",
"@openzeppelin/contracts": "^3.3.0-solc-0.7",
"@openzeppelin/contracts": "^3.3.0",
"@openzeppelin/test-helpers": "^0.5.10",
"@truffle/contract": "^4.0.31",
"@truffle/hdwallet-provider": "^1.1.0",
"chai": "^4.2.0",
Expand Down
86 changes: 86 additions & 0 deletions ethereum/scripts/burnWrappedDOT.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Example usage:
// truffle exec burnWrappedDOT.js [amount] [polkadot-recipient]
// truffle exec burnWrappedDOT.js [amount] [polkadot-recipient] --network ropsten

const DOTApp = artifacts.require("DOTApp");
const Token = artifacts.require("Token");

const BigNumber = require('bignumber.js');

const DOT_DECIMALS = 10;
const ETHER_DECIMALS = 18;

const wrapped = (amount) =>
amount.multipliedBy(BigNumber(10).exponentiatedBy(ETHER_DECIMALS - DOT_DECIMALS));

const unwrapped = (amount) =>
amount.dividedToIntegerBy(BigNumber(10).exponentiatedBy(ETHER_DECIMALS - DOT_DECIMALS));

module.exports = async () => {
// Parameters
const amount = process.argv[4].toString();
if (!amount) {
console.log("Must provide an amount (wrapped dots)")
return
}
const amountWei = web3.utils.toWei(amount, "ether");

const polkadotRecipient = process.argv[5].toString();
if (!polkadotRecipient) {
console.log("Must provide a Polkadot recipient")
return
}
const recipient = Buffer.from(polkadotRecipient.replace(/^0x/, ""), "hex");

try {
// Get current accounts
const accounts = await web3.eth.getAccounts();


const account = accounts[1];

const app = await DOTApp.deployed();
const token = await Token.at(await app.token());

let totalSupply = BigNumber(await token.totalSupply());
console.log("Total Supply ", unwrapped(totalSupply).toString());

let balanceWei = await token.balanceOf(account);
let balance = unwrapped(BigNumber(balanceWei));

console.log(`Balance for sending account ${account}: ${balanceWei} wei (${balance} wdot)`);

return;

const { logs } = await app.burn(
recipient,
amountWei,
0,
{
from: account,
gas: 300000 // 300,000 Gwei
}
);

console.log("Submitted transaction");

const event = logs.find(e => e.event === "Burned");
const event_decoded = {
[event.event]: {
sender: event.args.sender,
recipient: event.args.recipient,
amount: event.args.amount.toString(),
}
}
console.log("Events:")
console.log(JSON.stringify(event_decoded, null, 2));

balanceWei = await token.balanceOf(account);
balance = web3.utils.fromWei(balanceWei);
console.log(`Balance for ${account} is now: ${balanceWei} wei (${balance} wdot)`);

} catch (error) {
console.error({ error });
}
return;
};
3 changes: 2 additions & 1 deletion ethereum/test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ const encodeMessage = (message) => {
);
}

const deployAppContractWithChannels = async (appContract) => {
const deployAppContractWithChannels = async (appContract, ...appContractArgs) => {
const channels = {
basic: {
inbound: await channelContracts.basic.inbound.new(),
Expand All @@ -169,6 +169,7 @@ const deployAppContractWithChannels = async (appContract) => {
};

const app = await appContract.new(
...appContractArgs,
{
inbound: channels.basic.inbound.address,
outbound: channels.basic.outbound.address,
Expand Down
Loading

0 comments on commit 09b49c5

Please sign in to comment.