Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: deploy libraries and proxy contract as singletons #164

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c7bb62
chore(bridge): add source code separators
guidiaz May 12, 2021
5d7f870
chore(bridge): rearrange source contracts layout
guidiaz May 12, 2021
312cad8
refactor(bridge): enable proxy to become singleton
guidiaz May 12, 2021
dbbdfc1
chore(bridge): ignore .old folders
guidiaz May 12, 2021
e1f8ed1
feat(bridge): create factory based on erc-2470
guidiaz May 12, 2021
bd84600
feat(bridge): create singletons migration script
guidiaz May 12, 2021
2c6070f
chore(bridge): rearrange migration scripts
guidiaz May 12, 2021
1246bdf
feat(bridges): recreate `addresses.json` if empty
guidiaz May 12, 2021
ca30f8b
feat(proxy): upgrade proxy if new WRB is deployed
guidiaz May 12, 2021
25a7ffc
feat(bridge): new `addresses.json` structure
guidiaz May 12, 2021
4c396bb
fix(bridge): comment untested test
guidiaz May 12, 2021
9dd6c69
test(bridge): consider WRBproxy is now a singleton
guidiaz May 12, 2021
d26c2b5
chore(bridge): added solhint exception
guidiaz May 13, 2021
e269274
test(bridge): remove trailing whitespace
guidiaz May 13, 2021
af74bf6
fix(bridge): typo on the filename
guidiaz May 13, 2021
001c400
test(using_witnet): fix balance checks
mariocao May 17, 2021
ee40300
fix(bridge): corrections in migrations scripts
guidiaz May 18, 2021
d9c2df1
chore(bridge): reduce blank/superflous js lines
guidiaz May 18, 2021
510831b
chore(bridge): deprecate `utils.logs.trace`
guidiaz May 18, 2021
7e1dcd1
refactor(bridge): rename `utils/utils.js`
guidiaz May 18, 2021
3304df0
chore(bridge): attend @tommytrg code-style review
guidiaz May 18, 2021
5062be5
chore(bridge): comply w/ eslint rules
guidiaz May 18, 2021
34f5094
fix: resolve conflict due to incomplete rebase
guidiaz May 20, 2021
b2b00be
refactor(utils): move helper functions to ./utils
guidiaz May 20, 2021
a2d1087
fix: solve unseen eslint errors on migrations/**
guidiaz May 20, 2021
5371d82
chore: push singleton addresses
guidiaz May 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ contracts/flattened
.vscode

yarn.lock
package-lock.json
package-lock.json

.old
3 changes: 1 addition & 2 deletions contracts/UsingWitnet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
pragma solidity >=0.6.0 <0.7.0;

import "./Request.sol";
import "./Witnet.sol";
import "./WitnetRequestBoardProxy.sol";

import "./libs/Witnet.sol";

/**
* @title The UsingWitnet contract
Expand Down
30 changes: 20 additions & 10 deletions contracts/WitnetRequestBoardProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@ contract WitnetRequestBoardProxy {
// Array with the controllers that have been used in the Proxy
ControllerInfo[] internal controllers;


/// =======================================================================
/// --- Modifiers ---------------------------------------------------------

modifier notIdentical(address _newAddress) {
require(_newAddress != address(currentWitnetRequestBoard), "The provided Witnet Requests Board instance address is already in use");
_;
}

/**
* @notice Include an address to specify the Witnet Request Board.
* @param _witnetRequestBoardAddress WitnetRequestBoard address.
*/
constructor(address _witnetRequestBoardAddress) public {

/// =======================================================================
/// --- Constructor -------------------------------------------------------

/// @notice Constructor: initialize controllers list
constructor() public {
// Initialize the first epoch pointing to the first controller
controllers.push(ControllerInfo({controllerAddress: _witnetRequestBoardAddress, lastId: 0}));
currentWitnetRequestBoard = WitnetRequestBoardInterface(_witnetRequestBoardAddress);
controllers.push(ControllerInfo({controllerAddress: address(0), lastId: 0}));
}

/// @dev Posts a data request into the WRB in expectation that it will be relayed and resolved in Witnet with a total reward that equals to msg.value.
Expand Down Expand Up @@ -106,11 +110,17 @@ contract WitnetRequestBoardProxy {

/// @notice Upgrades the Witnet Requests Board if the current one is upgradeable.
/// @param _newAddress address of the new block relay to upgrade.
function upgradeWitnetRequestBoard(address _newAddress) external notIdentical(_newAddress) {
// Require the WRB is upgradable
require(currentWitnetRequestBoard.isUpgradable(msg.sender), "The upgrade has been rejected by the current implementation");
function upgradeWitnetRequestBoard(address _newAddress) public notIdentical(_newAddress) {
// Require current WRB to be upgradable:
require(
address(currentWitnetRequestBoard) == address(0)
|| currentWitnetRequestBoard.isUpgradable(msg.sender)
, "The upgrade has been rejected by the current implementation"
);

// Map the currentLastId to the corresponding witnetRequestBoardAddress and add it to controllers
controllers.push(ControllerInfo({controllerAddress: _newAddress, lastId: currentLastId}));

// Upgrade the WRB
currentWitnetRequestBoard = WitnetRequestBoardInterface(_newAddress);
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
73 changes: 73 additions & 0 deletions contracts/utils/SingletonFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.6.2;


/**
* @title Based on Singleton Factory (EIP-2470), authored by Guilherme Schmidt (Status Research & Development GmbH)
* @notice Exposes CREATE2 (EIP-1014) to deploy bytecode on deterministic addresses based on initialization code and salt.
* @dev Exposes also helper method to pre-determine contract address gieve
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
*/
contract SingletonFactory {
/**
* @notice Deploys `_initCode` using `_salt` for defining the deterministic address.
* @param _initCode Initialization code.
* @param _salt Arbitrary value to modify resulting address.
* @return createdContract Created contract address.
*/
function deploy(bytes memory _initCode, bytes32 _salt)
public
returns (address payable createdContract)
{
assembly {
createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt)
}
}

/**
* @notice Deploys a deterministic address based on `_initCode` and `_salt`.
using `_salt` for defining the deterministic address.
* @param _initCode Initialization code.
* @param _initCall Calldata to be made to created contract.
* @param _salt Arbitrary value to modify resulting address.
* @return createdContract Created contract address.
*/
function deployAndInit(bytes memory _initCode, bytes memory _initCall, bytes32 _salt)
public
returns (address payable createdContract)
{
assembly {
createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt)
}
if (_initCall.length > 0) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory reason) = createdContract.call(_initCall);
mariocao marked this conversation as resolved.
Show resolved Hide resolved
require(success, string(reason));
}
}


/**
* @notice Determine singleton contract address that might be created from this factory, given its `_initCode` and a `_salt`.
* @param _initCode Initialization code.
* @param _salt Arbitrary value to modify resulting address.
* @return expectedAddr Expected contract address.
*/
function determineAddr(bytes memory _initCode, bytes32 _salt)
public
view
returns (address)
{
return address(
uint160(uint(keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
_salt,
keccak256(_initCode)
)
)))
);
}
}
44 changes: 44 additions & 0 deletions migrations/01_initial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const fs = require("fs")
const Migrations = artifacts.require("Migrations")

module.exports = async function (deployer, network) {
if (!fs.existsSync("./migrations/addresses.json")) {
await fs.open("./migrations/addresses.json", "w", function (err, file) {
if (err) throw new Error("Fatal: cannot create ./migrations/addreses.json")
console.log("> Created ./migrations/addresses.json file.")
})
}

// Prepare "addresses.json" structure if necessary:
let addresses = await fs.readFileSync("./migrations/addresses.json")
if (addresses.length === 0) addresses = "{}"
addresses = JSON.parse(addresses)

let changes = false
if (!("networks" in addresses)) {
addresses.networks = {}
changes = true
}
if (!(network in addresses.networks)) {
addresses.networks[network] = {}
changes = true
}
if (!("singletons" in addresses)) {
addresses.singletons = {}
changes = true
}
if (!("libraries" in addresses.singletons)) {
addresses.singletons.libraries = {}
changes = true
}
if (!("contracts" in addresses.singletons)) {
addresses.singletons.contracts = {}
changes = true
}
if (changes) {
await fs.writeFileSync("./migrations/addresses.json", JSON.stringify(addresses, null, 2))
}

// Deploy "Migrations" contract:
await deployer.deploy(Migrations)
}
115 changes: 115 additions & 0 deletions migrations/02_singleton_libs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
const ethUtils = require("ethereumjs-util")
const fs = require("fs")
const utils = require("../utils")

const SingletonFactory = artifacts.require("SingletonFactory")

module.exports = async function (deployer, network, accounts) {
const addresses = require("./addresses.json")
const singletons = require("./singletons.json")

guidiaz marked this conversation as resolved.
Show resolved Hide resolved
const from = singletons.from || deployer.networks[network].from || accounts[0]

// Generate SingletonFactory deployment transaction:
const res = utils.generateDeployTx(
SingletonFactory.toJSON(),
singletons.sender.r,
singletons.sender.s,
singletons.sender.gasprice,
singletons.sender.gas
)

const deployedCode = await web3.eth.getCode(res.contractAddr)

if (deployedCode.length <= 3) {
guidiaz marked this conversation as resolved.
Show resolved Hide resolved
// Deploy SingletonFactory instance, if not yet deployed on this `network`:
utils.traceHeader("Inception of 'SingletonFactory':")

const balance = await web3.eth.getBalance(from)
const estimatedGas = res.gasLimit
const value = estimatedGas * res.gasPrice
let makerBalance = await web3.eth.getBalance(res.sender)

if (makerBalance < value) {
// transfer ETH funds to sender address, if currently not enough:
await web3.eth.sendTransaction({
from: from,
to: res.sender,
value: value - makerBalance,
})
makerBalance = await web3.eth.getBalance(res.sender)
}

const tx = await web3.eth.sendSignedTransaction(res.rawTx)
utils.traceTx(tx, web3.utils.fromWei((balance - await web3.eth.getBalance(from)).toString()))
} else {
utils.traceHeader("Singleton factory: 'SingletonFactory")
}

// Set SingletonFactory address on current network:
SingletonFactory.address = res.contractAddr
const factory = await SingletonFactory.deployed()

// Trace factory relevant data:
console.log(" ", "> sender's balance:\t",
`${web3.utils.fromWei((await web3.eth.getBalance(res.sender)).toString(), "ether")} ETH`
)
console.log(" ", "> factory codehash:\t", web3.utils.soliditySha3(await web3.eth.getCode(res.contractAddr)))
console.log(" ", "> factory sender:\t", res.sender)
console.log(" ", "> factory address:\t", factory.address)
console.log(" ", "> factory nonce:\t", await web3.eth.getTransactionCount(factory.address))
console.log("")

// Process all singleton libraries referred in config file:
for (const lib in singletons.libs) {
const artifact = artifacts.require(lib)
const salt = singletons.libs[lib].salt
? "0x" + ethUtils.setLengthLeft(ethUtils.toBuffer(singletons.libs[lib].salt), 32).toString("hex")
: "0x0"

let bytecode = artifact.toJSON().bytecode
if (singletons.libs[lib].links) {
singletons.libs[lib].links.forEach(
// Join dependent library address(es) into the library bytecode to be deployed:
// Please note: dependent libraries should have been previously deployed,
// so order in which libraries are declared in the config file actually matters.
sublib => {
const sublibArtifact = artifacts.require(sublib)
const sublibAddr = sublibArtifact.address.slice(2).toLowerCase()
const sublibMark = `__${sublibArtifact.contractName}${"_".repeat(38 - sublibArtifact.contractName.length)}`
bytecode = bytecode.split(sublibMark).join(sublibAddr)
}
)
}
artifact.bytecode = bytecode

const libAddr = await factory.determineAddr.call(bytecode, salt)

if ((await web3.eth.getCode(libAddr)).length <= 3) {
// Deploy library instance, if not yet deployed on this `network`:
utils.traceHeader(`Singleton inception of library '${lib}':`)

const balance = await web3.eth.getBalance(from)
const gas = singletons.libs[lib].gas || 10 ** 6
const tx = await factory.deploy(bytecode, salt, { from: from, gas: gas })
utils.traceTx(tx.receipt, web3.utils.fromWei((balance - await web3.eth.getBalance(from)).toString()))
} else {
utils.traceHeader(`Singleton library: '${lib}'`)
}

artifact.address = libAddr

const libCodehash = web3.utils.soliditySha3(await web3.eth.getCode(artifact.address))
if (!libCodehash) {
throw new Error(`Fatal: unable to deploy library '${lib}' as singleton. Try providing more gas.`)
}

console.log(" ", "> library codehash:\t", libCodehash)
console.log(" ", "> library address:\t", artifact.address)
console.log()

addresses.singletons.libraries[lib] = libAddr
}

fs.writeFileSync("./migrations/addresses.json", JSON.stringify(addresses, null, 2))
}
17 changes: 17 additions & 0 deletions migrations/03_new_wrb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const fs = require("fs")
const WitnetRequestBoard = artifacts.require("WitnetRequestBoard")

module.exports = async function (deployer, network, accounts) {
network = network.split("-")[0]

const addresses = require("./addresses.json")
if (network in addresses.networks && addresses.networks[network].WitnetRequestBoard) {
WitnetRequestBoard.address = addresses.networks[network].WitnetRequestBoard
} else {
console.log(`> Deploying new instance of 'WitnetRequestBoard' into '${network}' network...`)
await deployer.deploy(WitnetRequestBoard, [accounts[0]], { from: accounts[0] })
addresses.networks[network].WitnetRequestBoard = WitnetRequestBoard.address
}

fs.writeFileSync("./migrations/addresses.json", JSON.stringify(addresses, null, 2))
}
Loading