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

Secondary order authentication via global proxy #39

Closed
wants to merge 6 commits into from
Closed
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";

/**
* Construct a new GlobalMaker, creating the proxy it will require
*/
constructor (ProxyRegistry registry) public {
registry.registerProxy();
}

/**
* Check if a signature is valid
*
* @param _data Data signed over
* @param _signature Encoded signature
* @return magicValue Magic value if valid, zero-value otherwise
*/
function isValidSignature(
bytes memory _data,
bytes memory _signature,
bytes memory _extradata)
override
public
view
returns (bytes4 magicValue)
{
// assert signature
bytes memory sig = ArrayUtils.arrayTake(abi.encodeWithSignature("transferFrom(address,address,uint256)"), 4);
require(ArrayUtils.arrayEq(sig, ArrayUtils.arrayTake(_extradata, 4)));

// decode calldata
(address callFrom, address callTo, uint256 token) = abi.decode(ArrayUtils.arrayDrop(_extradata, 4), (address, address, uint256));

// check that the user (whoever is sending the tokens) signed the order hash
bytes32 hash = abi.decode(_data, (bytes32));
(uint8 v, bytes32 r, bytes32 s) = abi.decode(_signature, (uint8, bytes32, bytes32));
if (callFrom == ecrecover(hash, v, r, s)) {
return MAGICVALUE;
} else {
return SIGINVALID;
}
}

}
8 changes: 0 additions & 8 deletions contracts/exchange/Exchange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,6 @@ contract Exchange is ExchangeCore {
return validateOrderParameters(order, hashOrder(order));
}

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

function approveOrderHash_(bytes32 hash)
external
{
Expand Down
15 changes: 10 additions & 5 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 @@ -153,7 +154,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, Call memory call)
internal
view
returns (bool)
Expand Down Expand Up @@ -184,6 +185,10 @@ contract ExchangeCore is ReentrancyGuarded, StaticCaller, EIP712 {
/* (c): Contract-only authentication: EIP/ERC 1271. */
if (isContract) {

if (ERC1271Mod(maker).isValidSignature(abi.encodePacked(calculatedHashToSign), signature, call.data) == EIP_1271_MAGICVALUE) {
return true;
}

if (ERC1271(maker).isValidSignature(abi.encodePacked(calculatedHashToSign), signature) == EIP_1271_MAGICVALUE) {
return true;
}
Expand Down Expand Up @@ -321,10 +326,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), "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), "Second order failed authorization");
}

/* INTERACTIONS */
Expand Down Expand Up @@ -358,14 +363,14 @@ contract ExchangeCore is ReentrancyGuarded, StaticCaller, EIP712 {

/* EFFECTS */

/* Update first order fill, if necessary. */
/* Update first order fill, if necessary. When the sender is the maker, this is not done in order to save gas. */
if (firstOrder.maker != msg.sender) {
if (firstFill != previousFirstFill) {
fills[firstOrder.maker][firstHash] = firstFill;
}
}

/* Update second order fill, if necessary. */
/* Update second order fill, if necessary. When the sender is the maker, this is not done in order to save gas. */
if (secondOrder.maker != msg.sender) {
if (secondFill != previousSecondFill) {
fills[secondOrder.maker][secondHash] = secondFill;
Expand Down
32 changes: 32 additions & 0 deletions contracts/lib/EIP1271Mod.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*

<< EIP 1271 (modified) >>

*/

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
* @param _extradata Extra data which may be relevant to validation
*
* 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 _extradata)
virtual
public
view
returns (bytes4 magicValue);
}
7 changes: 6 additions & 1 deletion 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,7 +19,11 @@ module.exports = (deployer, network) => {
return deployer.deploy(WyvernExchange, chainIds[network], [WyvernRegistry.address, '0xa5409ec958C83C3f309868babACA7c86DCB077c1']).then(() => {
if (network !== 'development') setConfig('deployed.' + network + '.WyvernExchange', WyvernExchange.address)
return WyvernRegistry.deployed().then(registry => {
return registry.grantInitialAuthentication(WyvernExchange.address)
return registry.grantInitialAuthentication(WyvernExchange.address).then(() => {
return deployer.deploy(GlobalMaker, WyvernRegistry.address).then(() => {
if (network !== 'development') setConfig('deployed.' + network + '.GlobalMaker', GlobalMaker.address)
})
})
})
})
})
Expand Down
86 changes: 86 additions & 0 deletions test/7-global-maker-matching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* global artifacts:false, it:false, contract:false, assert:false */

const WyvernAtomicizer = artifacts.require('WyvernAtomicizer')
const WyvernExchange = artifacts.require('WyvernExchange')
const WyvernStatic = artifacts.require('WyvernStatic')
const WyvernRegistry = artifacts.require('WyvernRegistry')
const TestERC1155 = artifacts.require('TestERC1155')
const TestERC1271 = artifacts.require('TestERC1271')
const TestERC20 = artifacts.require('TestERC20')
const TestERC721 = artifacts.require('TestERC721')
const GlobalMaker = artifacts.require('GlobalMaker')

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

const {wrap,hashOrder,ZERO_BYTES32,randomUint,NULL_SIG,assertIsRejected} = require('./aux')

const CHAIN_ID = 50

contract('WyvernExchange', (accounts) =>
{
let deploy_core_contracts = async () =>
{
let [registry,atomicizer] = await Promise.all([WyvernRegistry.new(), WyvernAtomicizer.new(), GlobalMaker.new()])
let [exchange,statici] = await Promise.all([WyvernExchange.new(CHAIN_ID,[registry.address]),WyvernStatic.new(atomicizer.address)])
await registry.grantInitialAuthentication(exchange.address)
return {registry,exchange:wrap(exchange),atomicizer,statici}
}

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

it('matches erc1155 nft-nft swap order',async () =>
{
let account_a = accounts[0]
let account_b = accounts[6]

let {exchange, registry, statici} = await deploy_core_contracts()
let [erc1155, maker] = await deploy([TestERC1155, GlobalMaker])

await registry.registerProxy({from: account_a})
let proxy1 = await registry.proxies(account_a)
assert.equal(true, proxy1.length > 0, 'no proxy address for account a')

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

// what to do about approval from the maker contract
// should be the user account approving

await Promise.all([erc1155.setApprovalForAll(proxy1,true,{from: account_a}),erc1155.setApprovalForAll(proxy2,true,{from: account_b})])

let nfts = [{tokenId:4,amount:1},{tokenId:5,amount:1}]
await Promise.all([erc1155.mint(account_a,nfts[0].tokenId),erc1155.mint(account_b,nfts[1].tokenId)])

const erc1155c = new web3.eth.Contract(erc1155.abi, erc1155.address)
const selector = web3.eth.abi.encodeFunctionSignature('swapOneForOneERC1155(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')

const paramsOne = web3.eth.abi.encodeParameters(
['address[2]', 'uint256[2]', 'uint256[2]'],
[[erc1155.address, erc1155.address], [nfts[0].tokenId, nfts[1].tokenId], [nfts[0].amount, nfts[1].amount]]
)

const paramsTwo = web3.eth.abi.encodeParameters(
['address[2]', 'uint256[2]', 'uint256[2]'],
[[erc1155.address, erc1155.address], [nfts[1].tokenId, nfts[0].tokenId], [nfts[1].amount, nfts[0].amount]]
)

const one = {registry: registry.address, maker: account_a, staticTarget: statici.address, staticSelector: selector, staticExtradata: paramsOne, maximumFill: '1', listingTime: '0', expirationTime: '10000000000', salt: '7'}
const two = {registry: registry.address, maker: account_b, staticTarget: statici.address, staticSelector: selector, staticExtradata: paramsTwo, maximumFill: '1', listingTime: '0', expirationTime: '10000000000', salt: '8'}

const firstData = erc1155c.methods.safeTransferFrom(account_a, account_b, nfts[0].tokenId, nfts[0].amount, "0x").encodeABI() + ZERO_BYTES32.substr(2)
const secondData = erc1155c.methods.safeTransferFrom(account_b, account_a, nfts[1].tokenId, nfts[1].amount, "0x").encodeABI() + ZERO_BYTES32.substr(2)

const firstCall = {target: erc1155.address, howToCall: 0, data: firstData}
const secondCall = {target: erc1155.address, howToCall: 0, data: secondData}
const sigOne = NULL_SIG
let sigTwo = await exchange.sign(two, account_b)

await exchange.atomicMatch(one, sigOne, firstCall, two, sigTwo, secondCall, ZERO_BYTES32)
let [new_balance1,new_balance2] = await Promise.all([erc1155.balanceOf(account_a, nfts[1].tokenId),erc1155.balanceOf(account_b, nfts[0].tokenId)])
assert.isTrue(new_balance1.toNumber() > 0,'Incorrect owner')
assert.isTrue(new_balance2.toNumber() > 0,'Incorrect owner')
})

})