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

Global Maker #48

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
63 changes: 63 additions & 0 deletions contracts/GlobalMaker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*

<< Global Maker >>

*/

pragma solidity 0.7.5;

import "./lib/EIP1271Mod.sol";
import "./lib/ArrayUtils.sol";
import "./registry/ProxyRegistry.sol";

/**
* @title GlobalMaker
* @author Wyvern Protocol Developers
*/
contract GlobalMaker is ERC1271Mod
{
bytes4 constant internal SIGINVALID = 0x00000000;

string public constant name = "Global Maker";

mapping (bytes4 => uint16) public sigMakerOffsets;

/**
* Construct a new GlobalMaker, creating the proxy it will require
*/
constructor (ProxyRegistry registry, bytes4[] memory functionSignatures, uint16[] memory makerOffsets)
public
{
require(functionSignatures.length > 0,"No function signatures passed, GlobalMaker would be inert.");
require(functionSignatures.length == makerOffsets.length,"functionSignatures and makerOffsets lengths not equal");
registry.registerProxy();
for (uint index = 0 ; index < functionSignatures.length ; ++index)
sigMakerOffsets[functionSignatures[index]] = makerOffsets[index];
}

/**
* Check if a signature is valid
*
* @param _data Data signed over
* @param _signature Encoded signature
* @param _callData Original call data
* @return magicValue Magic value if valid, zero-value otherwise
*/
function isValidSignature(
bytes memory _data,
bytes memory _signature,
bytes memory _callData)
override
public
view
returns (bytes4 magicValue)
{
bytes4 sig = _callData[0] | bytes4(_callData[1]) >> 8 | bytes4(_callData[2]) >> 16 | bytes4(_callData[3]) >> 24;
require(sigMakerOffsets[sig] != 0x00000000,"Unknown function signature");
bytes32 hash = abi.decode(_data, (bytes32));
(address maker) = abi.decode(ArrayUtils.arraySlice(_callData,sigMakerOffsets[sig],32),(address));
(uint8 v, bytes32 r, bytes32 s) = abi.decode(_signature, (uint8, bytes32, bytes32));
return (maker == ecrecover(hash, v, r, s)) ? MAGICVALUE : SIGINVALID;
}

}
10 changes: 9 additions & 1 deletion contracts/exchange/Exchange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ contract Exchange is ExchangeCore {
view
returns (bool)
{
return validateOrderAuthorization(hash, maker, signature);
return validateOrderAuthorization(hash, maker, signature, "0x");
}

function validateOrderAuthorization_(bytes32 hash, address maker, bytes calldata signature, bytes memory callData)
external
view
returns (bool)
{
return validateOrderAuthorization(hash, maker, signature, callData);
}

function approveOrderHash_(bytes32 hash)
Expand Down
10 changes: 7 additions & 3 deletions contracts/exchange/ExchangeCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "../lib/StaticCaller.sol";
import "../lib/ReentrancyGuarded.sol";
import "../lib/EIP712.sol";
import "../lib/EIP1271.sol";
import "../lib/EIP1271Mod.sol";
import "../registry/ProxyRegistryInterface.sol";
import "../registry/AuthenticatedProxy.sol";

Expand Down Expand Up @@ -154,7 +155,7 @@ contract ExchangeCore is ReentrancyGuarded, StaticCaller, EIP712 {
return true;
}

function validateOrderAuthorization(bytes32 hash, address maker, bytes memory signature)
function validateOrderAuthorization(bytes32 hash, address maker, bytes memory signature, bytes memory callData)
internal
view
returns (bool)
Expand Down Expand Up @@ -184,6 +185,9 @@ contract ExchangeCore is ReentrancyGuarded, StaticCaller, EIP712 {

/* (c): Contract-only authentication: EIP/ERC 1271. */
if (isContract) {
if (ERC1271Mod(maker).isValidSignature(abi.encodePacked(calculatedHashToSign), signature, callData) == EIP_1271_MAGICVALUE) {
return true;
}
if (ERC1271(maker).isValidSignature(abi.encodePacked(calculatedHashToSign), signature) == EIP_1271_MAGICVALUE) {
return true;
}
Expand Down Expand Up @@ -327,10 +331,10 @@ contract ExchangeCore is ReentrancyGuarded, StaticCaller, EIP712 {
(bytes memory firstSignature, bytes memory secondSignature) = abi.decode(signatures, (bytes, bytes));

/* Check first order authorization. */
require(validateOrderAuthorization(firstHash, firstOrder.maker, firstSignature), "First order failed authorization");
require(validateOrderAuthorization(firstHash, firstOrder.maker, firstSignature, firstCall.data), "First order failed authorization");

/* Check second order authorization. */
require(validateOrderAuthorization(secondHash, secondOrder.maker, secondSignature), "Second order failed authorization");
require(validateOrderAuthorization(secondHash, secondOrder.maker, secondSignature, secondCall.data), "Second order failed authorization");
}

/* INTERACTIONS */
Expand Down
31 changes: 31 additions & 0 deletions contracts/lib/EIP1271Mod.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*

<< EIP 1271 Mod >>

*/

pragma solidity 0.7.5;

abstract contract ERC1271Mod {

// bytes4(keccak256("isValidSignature(bytes,bytes)")
bytes4 constant internal MAGICVALUE = 0x20c13b0b;

/**
* @dev Should return whether the signature provided is valid for the provided data
* @param _data Arbitrary length data signed on the behalf of address(this)
* @param _signature Signature byte array associated with _data
*
* MUST return the bytes4 magic value 0x20c13b0b when function passes.
* MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
* MUST allow external calls
*/
function isValidSignature(
bytes memory _data,
bytes memory _signature,
bytes memory _callData)
virtual
public
view
returns (bytes4 magicValue);
}
14 changes: 14 additions & 0 deletions migrations/3_wyvern_registry_and_exchange.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const WyvernRegistry = artifacts.require('./WyvernRegistry.sol')
const WyvernExchange = artifacts.require('./WyvernExchange.sol')
const GlobalMaker = artifacts.require('./GlobalMaker.sol')
const { setConfig } = require('./config.js')

const chainIds = {
Expand All @@ -18,10 +19,23 @@ const personalSignPrefixes = {
baobab: "\x19Klaytn Signed Message:\n"
}

const globalMakerSigMakerOffsets = [
{
sig: 'transferFrom(address,address,uint256)',
offset: 4
},
{
sig: 'safeTransferFrom(address,address,uint256,uint256,bytes)',
offset: 4
}
]

module.exports = async (deployer, network) => {
const web3 = GlobalMaker.interfaceAdapter.web3
const personalSignPrefix = personalSignPrefixes[network] || personalSignPrefixes['default']
await deployer.deploy(WyvernRegistry)
await deployer.deploy(WyvernExchange, chainIds[network], [WyvernRegistry.address, '0xa5409ec958C83C3f309868babACA7c86DCB077c1'], Buffer.from(personalSignPrefix,'binary'))
await deployer.deploy(GlobalMaker,WyvernRegistry.address,globalMakerSigMakerOffsets.map(a => web3.eth.abi.encodeFunctionSignature(a.sig)),globalMakerSigMakerOffsets.map(a => a.offset))
if (network !== 'development') {
setConfig('deployed.' + network + '.WyvernRegistry', WyvernRegistry.address)
setConfig('deployed.' + network + '.WyvernExchange', WyvernExchange.address)
Expand Down
7 changes: 3 additions & 4 deletions test/7-static-market-matching.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* global artifacts:false, it:false, contract:false, assert:false */

const WyvernAtomicizer = artifacts.require('WyvernAtomicizer')
const WyvernExchange = artifacts.require('WyvernExchange')
const StaticMarket = artifacts.require('StaticMarket')
const WyvernRegistry = artifacts.require('WyvernRegistry')
Expand All @@ -18,10 +17,10 @@ contract('WyvernExchange', (accounts) =>
{
let deploy_core_contracts = async () =>
{
let [registry,atomicizer] = await Promise.all([WyvernRegistry.new(), WyvernAtomicizer.new()])
let [registry] = await Promise.all([WyvernRegistry.new()])
let [exchange,statici] = await Promise.all([WyvernExchange.new(CHAIN_ID,[registry.address],'0x'),StaticMarket.new()])
await registry.grantInitialAuthentication(exchange.address)
return {registry,exchange:wrap(exchange),atomicizer,statici}
return {registry,exchange:wrap(exchange),statici}
}

let deploy = async contracts => Promise.all(contracts.map(contract => contract.new()))
Expand Down Expand Up @@ -66,7 +65,7 @@ contract('WyvernExchange', (accounts) =>
const erc20c = new web3.eth.Contract(erc20.abi, erc20.address)
const selectorOne = web3.eth.abi.encodeFunctionSignature('anyERC1155ForERC20(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')
const selectorTwo = web3.eth.abi.encodeFunctionSignature('anyERC20ForERC1155(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')

const paramsOne = web3.eth.abi.encodeParameters(
['address[2]', 'uint256[3]'],
[[erc1155.address, erc20.address], [tokenId, sellingNumerator || 1, sellingPrice]]
Expand Down
83 changes: 83 additions & 0 deletions test/8-global-maker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* global artifacts:false, it:false, contract:false, assert:false */

const WyvernExchange = artifacts.require('WyvernExchange')
const StaticMarket = artifacts.require('StaticMarket')
const GlobalMaker = artifacts.require('GlobalMaker')
const WyvernRegistry = artifacts.require('WyvernRegistry')
const TestERC20 = artifacts.require('TestERC20')
const TestERC1155 = artifacts.require('TestERC1155')

const Web3 = require('web3')
const provider = new Web3.providers.HttpProvider('http://localhost:8545')
const web3 = new Web3(provider)

const {wrap,ZERO_BYTES32,CHAIN_ID,assertIsRejected,globalMakerSigMakerOffsets} = require('./aux')

contract('GlobalMaker', (accounts) =>
{
let deploy_core_contracts = async () =>
{
let [registry] = await Promise.all([WyvernRegistry.new()])
let [exchange,statici,globalMaker] = await Promise.all(
[
WyvernExchange.new(CHAIN_ID,[registry.address],'0x'),
StaticMarket.new(),
GlobalMaker.new(registry.address,globalMakerSigMakerOffsets.map(a => a.sig),globalMakerSigMakerOffsets.map(a => a.offset))
])
await registry.grantInitialAuthentication(exchange.address)
return {registry,exchange:wrap(exchange),statici,globalMaker}
}

let deploy = async contracts => Promise.all(contracts.map(contract => contract.new()))

it('matches erc1155 nft-nft swap order',async () =>
{
let account_a = accounts[1]
let account_b = accounts[2]
let nftId = 4
let nftAmount = 1
let erc20Amount = 20

let {exchange, registry, statici, globalMaker} = await deploy_core_contracts()
let [erc20,erc1155] = await deploy([TestERC20,TestERC1155])

let globalMakerProxy = await registry.proxies(globalMaker.address)
assert.equal(true, globalMakerProxy.length > 0, 'no proxy address for global maker')

await Promise.all([erc1155.setApprovalForAll(globalMakerProxy,true,{from: account_a}),erc20.approve(globalMakerProxy,erc20Amount,{from: account_b})])
await Promise.all([erc1155.mint(account_a,nftId,nftAmount),erc20.mint(account_b,erc20Amount)])

const erc1155c = new web3.eth.Contract(erc1155.abi, erc1155.address)
const erc20c = new web3.eth.Contract(erc20.abi, erc20.address)

const selectorOne = web3.eth.abi.encodeFunctionSignature('anyERC1155ForERC20(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')
const selectorTwo = web3.eth.abi.encodeFunctionSignature('anyERC20ForERC1155(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')

const paramsOne = web3.eth.abi.encodeParameters(
['address[2]', 'uint256[3]'],
[[erc1155.address, erc20.address], [nftId, nftAmount, erc20Amount]]
)

const paramsTwo = web3.eth.abi.encodeParameters(
['address[2]', 'uint256[3]'],
[[erc20.address, erc1155.address], [nftId, erc20Amount, nftAmount]]
)

const one = {registry: registry.address, maker: globalMaker.address, staticTarget: statici.address, staticSelector: selectorOne, staticExtradata: paramsOne, maximumFill: nftAmount, listingTime: '0', expirationTime: '10000000000', salt: '7'}
const two = {registry: registry.address, maker: globalMaker.address, staticTarget: statici.address, staticSelector: selectorTwo, staticExtradata: paramsTwo, maximumFill: erc20Amount, listingTime: '0', expirationTime: '10000000000', salt: '8'}

const firstData = erc1155c.methods.safeTransferFrom(account_a, account_b, nftId, nftAmount, "0x").encodeABI() + ZERO_BYTES32.substr(2)
const secondData = erc20c.methods.transferFrom(account_b, account_a, erc20Amount).encodeABI()

const firstCall = {target: erc1155.address, howToCall: 0, data: firstData}
const secondCall = {target: erc20.address, howToCall: 0, data: secondData}

const sigOne = await exchange.sign(one, account_a)
const sigTwo = await exchange.sign(two, account_b)

await exchange.atomicMatchWith(one, sigOne, firstCall, two, sigTwo, secondCall, ZERO_BYTES32,{from:accounts[6]})
let [new_balance1,new_balance2] = await Promise.all([erc20.balanceOf(account_a),erc1155.balanceOf(account_b, nftId)])
assert.isTrue(new_balance1.toNumber() > 0,'Incorrect owner')
assert.isTrue(new_balance2.toNumber() > 0,'Incorrect owner')
})
})
14 changes: 13 additions & 1 deletion test/aux.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ const ZERO_BYTES32 = '0x00000000000000000000000000000000000000000000000000000000
const NULL_SIG = {v: 27, r: ZERO_BYTES32, s: ZERO_BYTES32}
const CHAIN_ID = 50

const globalMakerSigMakerOffsets = [
{
sig: web3.eth.abi.encodeFunctionSignature('transferFrom(address,address,uint256)'),
offset: 4
},
{
sig: web3.eth.abi.encodeFunctionSignature('safeTransferFrom(address,address,uint256,uint256,bytes)'),
offset: 4
}
]

module.exports = {
hashOrder,
hashToSign,
Expand All @@ -182,5 +193,6 @@ module.exports = {
ZERO_ADDRESS,
ZERO_BYTES32,
NULL_SIG,
CHAIN_ID
CHAIN_ID,
globalMakerSigMakerOffsets
}