diff --git a/contracts/TestSmartContractWallet.sol b/contracts/TestSmartContractWallet.sol new file mode 100644 index 0000000..2d920d8 --- /dev/null +++ b/contracts/TestSmartContractWallet.sol @@ -0,0 +1,57 @@ +/* + << TestSmartContractWallet >> +*/ + +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; + +contract ExchangeInterface{ + function approveOrder_(address registry, address maker, address staticTarget, bytes4 staticSelector, bytes calldata staticExtradata, uint maximumFill, uint listingTime, uint expirationTime, uint salt, bool orderbookInclusionDesired) + external{} +} +contract ProxyInterface{ + function registerProxy() + external{} +} + +/** + * @title TestSmartContractWallet + * @dev Test contract for Smart contract wallets, proxies some calls an EOA wallet would make to setup on wyvern. + */ +contract TestSmartContractWallet { + + event Deposit(address indexed _from, uint indexed _id, uint _value); + + constructor () public { + } + + // Called by atomicMatch when this contract is taker for an order with eth value exchanged. + receive() external payable { + // Use more than 2300 gas to test gas limit for send and transfer + emit Deposit(msg.sender, 0, msg.value); + emit Deposit(msg.sender, 1, msg.value); + emit Deposit(msg.sender, 2, msg.value); + } + + // Proxy to exchange + function approveOrder_(address exchange, address registry, address maker, address staticTarget, bytes4 staticSelector, bytes calldata staticExtradata, uint maximumFill, uint listingTime, uint expirationTime, uint salt, bool orderbookInclusionDesired) + public returns (bool) { + ExchangeInterface(exchange).approveOrder_(registry, maker, staticTarget, staticSelector, staticExtradata, maximumFill, listingTime, expirationTime, salt, orderbookInclusionDesired); + return true; + } + + // Proxy to registry + function registerProxy(address registry) + public returns (bool) { + ProxyInterface(registry).registerProxy(); + return true; + } + + // Proxy to erc721 + function setApprovalForAll(address registry, address erc721, bool approved) + public returns (bool) { + ERC721(erc721).setApprovalForAll(registry, approved); + return true; + } +} diff --git a/contracts/exchange/ExchangeCore.sol b/contracts/exchange/ExchangeCore.sol index f627444..43f3f24 100644 --- a/contracts/exchange/ExchangeCore.sol +++ b/contracts/exchange/ExchangeCore.sol @@ -338,7 +338,9 @@ contract ExchangeCore is ReentrancyGuarded, StaticCaller, EIP712 { /* Transfer any msg.value. This is the first "asymmetric" part of order matching: if an order requires Ether, it must be the first order. */ if (msg.value > 0) { - address(uint160(firstOrder.maker)).transfer(msg.value); + /* Reentrancy prevented by reentrancyGuard modifier */ + (bool success,) = address(uint160(firstOrder.maker)).call{value: msg.value}(""); + require(success, "native token transfer failed."); } /* Execute first call, assert success. diff --git a/migrations/2_misc.js b/migrations/2_misc.js index 0029014..5313933 100644 --- a/migrations/2_misc.js +++ b/migrations/2_misc.js @@ -6,6 +6,7 @@ const StaticMarket = artifacts.require('./StaticMarket.sol') const TestERC20 = artifacts.require('./TestERC20.sol') const TestERC721 = artifacts.require('./TestERC721.sol') const TestERC1271 = artifacts.require('./TestERC1271.sol') +const TestSmartContractWallet = artifacts.require('./TestSmartContractWallet.sol') const TestAuthenticatedProxy = artifacts.require('./TestAuthenticatedProxy.sol') const { setConfig } = require('./config.js') @@ -28,12 +29,14 @@ module.exports = async (deployer, network) => { await deployer.deploy(TestERC721) await deployer.deploy(TestAuthenticatedProxy) await deployer.deploy(TestERC1271) + await deployer.deploy(TestSmartContractWallet) if (network !== 'development') { setConfig('deployed.' + network + '.TestERC20', TestERC20.address) setConfig('deployed.' + network + '.TestERC721', TestERC721.address) setConfig('deployed.' + network + '.TestAuthenticatedProxy', TestAuthenticatedProxy.address) setConfig('deployed.' + network + '.TestERC1271', TestERC1271.address) + setConfig('deployed.' + network + '.TestSmartContractWallet', TestSmartContractWallet.address) } } diff --git a/test/5-wyvern-exchange-matching.js b/test/5-wyvern-exchange-matching.js index 0a91571..875e097 100755 --- a/test/5-wyvern-exchange-matching.js +++ b/test/5-wyvern-exchange-matching.js @@ -7,6 +7,7 @@ const WyvernRegistry = artifacts.require('WyvernRegistry') const TestERC20 = artifacts.require('TestERC20') const TestERC721 = artifacts.require('TestERC721') const TestERC1271 = artifacts.require('TestERC1271') +const TestSmartContractWallet = artifacts.require('TestSmartContractWallet') const Web3 = require('web3') const provider = new Web3.providers.HttpProvider('http://localhost:8545') @@ -19,9 +20,9 @@ contract('WyvernExchange', (accounts) => { const withContracts = async () => { - let [exchange,statici,registry,atomicizer,erc20,erc721,erc1271] = await deploy( - [WyvernExchange,WyvernStatic,WyvernRegistry,WyvernAtomicizer,TestERC20,TestERC721,TestERC1271]) - return {exchange:wrap(exchange),statici,registry,atomicizer,erc20,erc721,erc1271} + let [exchange,statici,registry,atomicizer,erc20,erc721,erc1271,smartContractWallet] = await deploy( + [WyvernExchange,WyvernStatic,WyvernRegistry,WyvernAtomicizer,TestERC20,TestERC721,TestERC1271,TestSmartContractWallet]) + return {exchange:wrap(exchange),statici,registry,atomicizer,erc20,erc721,erc1271,smartContractWallet} } // Returns an array of two NFTs, one to give and one to get @@ -558,6 +559,28 @@ contract('WyvernExchange', (accounts) => { assert.isOk(await exchange.atomicMatchWith(one, oneSig, call, two, twoSig, call, ZERO_BYTES32, {value: 200})) }) + it('allows proxy registration for smart contract',async () => { + let {registry, erc721, smartContractWallet} = await withContracts() + // this registration carries over to the following test and is necessary for the value exchange. + await smartContractWallet.registerProxy(registry.address, {from: accounts[6]}) + let proxy = await registry.proxies(smartContractWallet.address) + assert.isTrue(proxy.length > 0,'No proxy address') + assert.isOk(await smartContractWallet.setApprovalForAll(proxy, erc721.address, true, {from: accounts[6]})) + }) + + it('should match with approvals and value to contract',async () => { + const value = 200 + let {exchange, registry, statici, smartContractWallet} = await withContracts() + const selector = web3.eth.abi.encodeFunctionSignature('any(bytes,address[7],uint8[2],uint256[6],bytes,bytes)') + const one = {registry: registry.address, maker: accounts[6], staticTarget: statici.address, staticSelector: selector, staticExtradata: '0x', maximumFill: '1', listingTime: '0', expirationTime: '100000000000', salt: randomUint()} + const two = {registry: registry.address, maker: smartContractWallet.address, staticTarget: statici.address, staticSelector: selector, staticExtradata: '0x', maximumFill: '1', listingTime: '0', expirationTime: '100000000000', salt: randomUint()} + + await Promise.all([exchange.approveOrder(one, false, {from: accounts[6]}),smartContractWallet.approveOrder_(exchange.inst.address, two.registry, two.maker, two.staticTarget, two.staticSelector, two.staticExtradata, two.maximumFill, two.listingTime, two.expirationTime, two.salt, false, {from: accounts[6]})]) + const call = {target: statici.address, howToCall: 0, data: web3.eth.abi.encodeFunctionSignature('test()')} + assert.isOk(await exchange.atomicMatchWith(two, NULL_SIG, call, one, NULL_SIG, call, ZERO_BYTES32, {value: value, from: accounts[6]})) + assert.equal(await web3.eth.getBalance(smartContractWallet.address), value.toString()) + }) + it('matches orders signed with personal_sign',async () => { let {exchange, registry, statici} = await withContracts() const selector = web3.eth.abi.encodeFunctionSignature('any(bytes,address[7],uint8[2],uint256[6],bytes,bytes)')