Skip to content

Commit

Permalink
Send ether with .call to support smart contract takers (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
stewart-lore authored Mar 15, 2022
1 parent b20ae78 commit 4790c04
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 4 deletions.
57 changes: 57 additions & 0 deletions contracts/TestSmartContractWallet.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
4 changes: 3 additions & 1 deletion contracts/exchange/ExchangeCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions migrations/2_misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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)
}
}

29 changes: 26 additions & 3 deletions test/5-wyvern-exchange-matching.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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
Expand Down Expand Up @@ -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)')
Expand Down

0 comments on commit 4790c04

Please sign in to comment.